Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Error Handling in Javascript

Error Handling in Javascript

How our software deals with errors is part of the UX.

So, why not design for errors as we do for all other critical parts of our software?

ErrorHandler - a soon to be open sourced library - is an effort to engineer error handling in Javascript.

39faa5e8e938d2fbd92acc2e8ebdf65b?s=128

Stratos Pavlakis

November 24, 2017
Tweet

Transcript

  1. ERROR HANDLING MADE EASY JS VERSION

  2. Stratos Pavlakis Lead Engineer @Workable @th3hunt

  3. Error Handling

  4. Why bother?

  5. Why bother? Cause good error handling builds TRUST 
 between

    our users and our product
  6. Why bother? Cause good error handling builds TRUST 
 between

    our users and our product It IS part of the UX
  7. Why bother? Cause good error handling builds TRUST 
 between

    our users and our product It IS part of the UX
  8. errors for developers

  9. NullPointerException Java

  10. RxCocoa.RxCocoaURLError Swift

  11. e Javascript

  12. err Javascript

  13. InvalidLoginError Problem Domain

  14. errors for users?

  15. None
  16. WHAT’S WRONG WITH THAT?

  17. – Younger self “Fix the copy and you’re set”

  18. Is that the case?

  19. Is that the case? Why do we keep getting this

    kind of 
 cryptic messages and bad UX? I asked at some point…
  20. None
  21. WE DON’T DESIGN FOR ERROR

  22. Designing for errors

  23. None
  24. None
  25. DESIGNING FOR ERRORS what does that mean? Take #1

  26. DESIGNING FOR ERRORS MEANS… to anticipate errors!

  27. DESIGNING FOR ERRORS MEANS… ▸ List and identify them during

    design meetings to anticipate errors!
  28. DESIGNING FOR ERRORS MEANS… ▸ List and identify them during

    design meetings ▸ Getting the product team involved to anticipate errors!
  29. – Younger self “I got a solid theory… Let’s do

    this!”
  30. “In theory there is no difference between theory and practice…

  31. – Jan L. A. van de Snepscheut “In theory there

    is no difference between theory and practice… In practice there is.”
  32. A story of errors

  33. A story of errors inspired from true events

  34. A STORY OF ERRORS WALTER

  35. A STORY OF ERRORS WALTER ▸ Senior Engineer

  36. A STORY OF ERRORS WALTER ▸ Senior Engineer ▸ Anticipates

    all errors.
  37. A STORY OF ERRORS INSTANT BUY

  38. A STORY OF ERRORS INSTANT BUY Walter adds error handling

    an early feature of the web application.
  39. A STORY OF ERRORS INSTANT BUY Walter adds error handling

    an early feature of the web application. • Unauthenticated
  40. A STORY OF ERRORS INSTANT BUY Walter adds error handling

    an early feature of the web application. • Unauthenticated • Out of stock
  41. A STORY OF ERRORS INSTANT BUY Walter adds error handling

    an early feature of the web application. • Unauthenticated • Out of stock • CSRF
  42. A STORY OF ERRORS INSTANT BUY Walter adds error handling

    an early feature of the web application. • Unauthenticated • Out of stock • CSRF • Offline
  43. A STORY OF ERRORS INSTANT BUY Walter adds error handling

    an early feature of the web application. • Unauthenticated • Out of stock • CSRF • Offline • Unknown
  44. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { });
  45. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { });
  46. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break;
  47. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break;
  48. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: alert(`You seem to be offline. No worries…’); break;
  49. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: alert(`You seem to be offline. No worries…’); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; }
  50. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: alert(`You seem to be offline. No worries…’); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; } default: alert('The operation is unavailable'); }
  51. A STORY OF ERRORS INSTANT BUY instantBuy(item) .then(() => thanksForBuying(item))

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: alert(`You seem to be offline. No worries…’); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; } default: alert('The operation is unavailable'); } logError(error);
  52. A STORY OF ERRORS JESSE

  53. A STORY OF ERRORS JESSE ▸ Junior Engineer

  54. A STORY OF ERRORS JESSE ▸ Junior Engineer ▸ Hired

    2 weeks ago
  55. A STORY OF ERRORS JESSE ▸ Junior Engineer ▸ Hired

    2 weeks ago ▸ Anticipates no errors. At all!
  56. A STORY OF ERRORS SAVE FOR LATER

  57. A STORY OF ERRORS SAVE FOR LATER Jesse adds no

    error handling to the Nth feature of the web application… still 1st one for him.
  58. A STORY OF ERRORS SAVE FOR LATER

  59. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi
  60. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated
  61. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated • Out of stock
  62. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated • Out of stock • CSRF
  63. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated • Out of stock • CSRF • Offline
  64. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated • Out of stock • CSRF • Offline • Unknown
  65. A STORY OF ERRORS SAVE FOR LATER After a couple

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated • Out of stock • CSRF • Offline • Unknown with a twist - silently sync when online
  66. saveForLater(item) .catch(error => { A STORY OF ERRORS SAVE FOR

    LATER });
  67. saveForLater(item) .catch(error => { A STORY OF ERRORS });

  68. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

    (error.status) { case 401: requestSignIn(); break;
  69. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

    (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break;
  70. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

    (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: onlineTasks.push(() => saveForLater(item)); break;
  71. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

    (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: onlineTasks.push(() => saveForLater(item)); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; }
  72. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

    (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: onlineTasks.push(() => saveForLater(item)); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; } default: alert('The operation is unavailable'); }
  73. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

    (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: onlineTasks.push(() => saveForLater(item)); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; } default: alert('The operation is unavailable'); } logError(error);
  74. saveForLater(item) .catch(error => { A STORY OF ERRORS copy-paste has

    a twist - swallow & send when online }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: onlineTasks.push(() => saveForLater(item)); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; } default: alert('The operation is unavailable'); } logError(error);
  75. None
  76. None
  77. None
  78. None
  79. None
  80. We end up with..

  81. We end up with.. Boilerplate, Repetition

  82. We end up with.. Boilerplate, Repetition No standard way of

    handling errors
  83. We end up with.. Boilerplate, Repetition No standard way of

    handling errors Cognitive overhead
  84. We end up with.. Boilerplate, Repetition No standard way of

    handling errors Cognitive overhead Inefficient error reporting
  85. We end up with.. Boilerplate, Repetition No standard way of

    handling errors Cognitive overhead Inefficient error reporting Friction in development cycles
  86. None
  87. None
  88. DESIGNING FOR ERRORS what does that mean? Take #2

  89. DESIGNING FOR ERRORS MEANS… (REVISED)

  90. DESIGNING FOR ERRORS MEANS… (REVISED) ▸ Anticipating errors

  91. DESIGNING FOR ERRORS MEANS… (REVISED) ▸ Anticipating errors ▸ Getting

    the product team involved
  92. DESIGNING FOR ERRORS MEANS… (REVISED) ▸ Anticipating errors ▸ Getting

    the product team involved ▸ Making error handling easy by engineering it
  93. DESIGNING FOR ERRORS checkpoint

  94. DESIGNING FOR ERRORS checkpoint what should guide our solution?

  95. DESIGNING FOR ERRORS KINDS OF ERRORS

  96. DESIGNING FOR ERRORS KINDS OF ERRORS - Business ‣ authentication

    ‣ validation ‣ stale data
  97. DESIGNING FOR ERRORS KINDS OF ERRORS - Business ‣ authentication

    ‣ validation ‣ stale data - Non business ‣ network ‣ storage ‣ device i/o (camera/mic/speakers/GPS) ‣ bugs
  98. DESIGNING FOR ERRORS KINDS OF ERRORS - Business ‣ authentication

    ‣ validation ‣ stale data - Non business ‣ network ‣ storage ‣ device i/o (camera/mic/speakers/GPS) ‣ bugs
  99. DESIGNING FOR ERRORS KINDS OF ERRORS - Business ‣ authentication

    ‣ validation ‣ stale data - Non business ‣ network ‣ storage ‣ device i/o (camera/mic/speakers/GPS) ‣ bugs cross-cut, common errors
  100. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Present ▸ Shy ▸ Comprehensible ▸ Actionable ▸ Targeted ▸ Reportable
  101. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Shy ▸ Comprehensible ▸ Actionable ▸ Targeted ▸ Reportable ▸ Is in place, timely and consistent
  102. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Comprehensible ▸ Actionable ▸ Targeted ▸ Reportable ▸ Is in place, timely and consistent ▸ Communicates errors only if necessary
  103. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Actionable ▸ Targeted ▸ Reportable ▸ Is in place, timely and consistent ▸ Communicates errors only if necessary ▸ Communicates errors in a comprehensible way
  104. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Targeted ▸ Reportable ▸ Is in place, timely and consistent ▸ Communicates errors only if necessary ▸ Communicates errors in a comprehensible way ▸ Suggests how to resolve if possible
  105. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Reportable ▸ Is in place, timely and consistent ▸ Communicates errors only if necessary ▸ Communicates errors in a comprehensible way ▸ Suggests how to resolve if possible ▸ Makes errors have the smallest area of effect
  106. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

    Is in place, timely and consistent ▸ Communicates errors only if necessary ▸ Communicates errors in a comprehensible way ▸ Suggests how to resolve if possible ▸ Makes errors have the smallest area of effect ▸ Lets the product owners & engineers know
  107. and we need it to be easy

  108. and we need it to be easy easy as in

  109. Error handling as easy as ordering a burger

  110. lettuce bun tomato bacon cheddar onion patty

  111. “Can I have a burger with baked flour, salted cured

    pork, relatively hard off-white natural cheese, tomato fruit and minced beef meat please?” - noone ever
  112. Instead we say… “The usual…” or “Cowboy burger with extra

    bacon without the onions”
  113. What we actually want

  114. ✓ Natural language What we actually want

  115. ✓ “offline” instead of “err.status === 0 || err.msg…” What

    we actually want
  116. ✓ “offline” instead of “err.status === 0 || err.msg…” ✓

    Presets of default actions for common errors What we actually want
  117. ✓ “offline” vs “err.status === 0 || err.msg…” ✓ Presets

    of default actions for common errors ✓ Customise presets in a dynamic way ➡ Add actions ➡ Override actions ➡ Skip actions ➡ Log or… not What we actually want
  118. ErrorHandler A library that provides a declarative fluent API for

    flexible error handling
  119. ERROR HANDLER LET’S TAKE A LOOK ErrorHandler .build() .when(‘out-of-stock’, notifyOutOfStock)

    .when(‘unauthenticated’, promptForSignIn) .when(‘offline’, promptToRetry) .when(422, {type: ‘stale-pay-info’}, getPaymentInfo) .when(430, refreshCSRFToken) .when(err => err instanceof KaboomError, onKaboom) .whenNot('offline', sendToAnalytics) .always(enableSubmit) .logWith(new SentryLogger()) .handle(err);
  120. ERROR HANDLER BASIC API when(matcher, action) alias(name, matcher) when(alias, action)

    when(alias, options, action) whenNot(matcher, action) otherwise(action) always(action) logWith(logger)
  121. ERROR HANDLER BASIC API handle(err, options) run(function) retry()

  122. ERROR HANDLER BASIC API - ALIAS ErrorHandler .alias('offline', err =>

    err.status === 0) ErrorHandler .alias( (tag) => typeof tag === 'number', (tag, err) => err.status === tag ) ErrorHandler .default() .when('offline', waitToGetOnline) .when(401, requestSignIn) .when(CustomError, onCustomError) .handle(err)
  123. ERROR HANDLER MOAAR API ErrorHandler .default() .when(‘foo', (err, handler) =>

    { handleFoo(); handler.skipFollowing(); }) .when(err => err.match(/fo/), foHappened) .when('bar', (err, handler) => { handleBar(); handler.skipDefaults(); }) .when('validation', ‘max-length-exceeded‘, handleMaxLen) .when('validation', ‘profanity‘, handleProfanity) .when(KaboomError, kaboomHappened) .always(() => enableSubmitButton()) .logWith(new SentryLogger()) .run(submitLoginForm);
  124. Our strategy

  125. Our strategy Setup a default ErrorHandler once

  126. Our strategy Setup a default ErrorHandler once Customise the default

    ErrorHandler when needed based on the context
  127. Most of the time all we will need is… }

    catch (err) { } ErrorHandler.default().handle(err);
  128. and sometimes we need… } catch (err) { } ErrorHandler

    .default() .when(‘foo', onFoo) .when('bar', onBar) .handle(err);
  129. None
  130. GANDALF APPROVES

  131. Back to our story

  132. saveForLater(item) .catch(error => { A STORY OF ERRORS copy-paste /

    default throughout the app has a twist }); switch (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break; case 0: onlineTasks.push(() => saveForLater(item)); break; case 422: if (error.response === 'out of stock') { this.showOutOfStockDialog(); break; } default: alert('The operation is unavailable'); } logError(error);
  133. turns to

  134. A STORY OF ERRORS ErrorHandler .default() .when('offline', () => {

    onlineTasks.push(() => saveForLater(item)); }) .handle(err); saveForLater(item) .catch(error => { });
  135. We get to.. Boilerplate, Repetition No standard way of handling

    Cognitive overhead Inefficient error reporting Friction in development cycles
  136. We get to.. No standard way of handling Cognitive overhead

    Inefficient error reporting Zero boilerplate, minimum repetition Friction in development cycles
  137. We get to.. Cognitive overhead Inefficient error reporting Zero boilerplate,

    minimum repetition Very standard way of handling errors Friction in development cycles
  138. We get to.. Inefficient error reporting Zero boilerplate, minimum repetition

    Very standard way of handling errors Readable domain related code Friction in development cycles
  139. We get to.. Zero boilerplate, minimum repetition Very standard way

    of handling errors Readable domain related code Efficient error tracking Friction in development cycles
  140. We get to.. Zero boilerplate, minimum repetition Very standard way

    of handling errors Readable domain related code Efficient error tracking One less problem to deal with
  141. – Swift Apprentice, Chapter 22 (Error Handling) “Error handling is

    the art of failing gracefully”
  142. Any Questions? No error you can’t .handle() Swift - https://github.com/Workable/swift-error-handler.git

    Java - https://github.com/Workable/java-error-handler.git Javascript - coming soon