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.

Stratos Pavlakis

November 24, 2017
Tweet

More Decks by Stratos Pavlakis

Other Decks in Programming

Transcript

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

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

    our users and our product It IS part of the UX
  3. Is that the case? Why do we keep getting this

    kind of 
 cryptic messages and bad UX? I asked at some point…
  4. DESIGNING FOR ERRORS MEANS… ▸ List and identify them during

    design meetings ▸ Getting the product team involved to anticipate errors!
  5. – Jan L. A. van de Snepscheut “In theory there

    is no difference between theory and practice… In practice there is.”
  6. A STORY OF ERRORS INSTANT BUY Walter adds error handling

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

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

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

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

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

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

    .catch(error => { }); switch (error.status) { case 401: requestSignIn(); break;
  13. 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;
  14. 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;
  15. 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; }
  16. 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'); }
  17. 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);
  18. A STORY OF ERRORS JESSE ▸ Junior Engineer ▸ Hired

    2 weeks ago ▸ Anticipates no errors. At all!
  19. 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.
  20. A STORY OF ERRORS SAVE FOR LATER After a couple

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

    of code reviews, QA testing and a demo with bad WiFi • Unauthenticated
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. saveForLater(item) .catch(error => { A STORY OF ERRORS }); switch

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

    (error.status) { case 401: requestSignIn(); break; case 430: // CSRF promptToRefreshSession(); break;
  29. 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;
  30. 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; }
  31. 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'); }
  32. 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);
  33. 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);
  34. We end up with.. Boilerplate, Repetition No standard way of

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

    handling errors Cognitive overhead Inefficient error reporting Friction in development cycles
  36. DESIGNING FOR ERRORS MEANS… (REVISED) ▸ Anticipating errors ▸ Getting

    the product team involved ▸ Making error handling easy by engineering it
  37. DESIGNING FOR ERRORS KINDS OF ERRORS - Business ‣ authentication

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

    ‣ validation ‣ stale data - Non business ‣ network ‣ storage ‣ device i/o (camera/mic/speakers/GPS) ‣ bugs
  39. 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
  40. DESIGNING FOR ERRORS WE NEED ERROR HANDLING THAT IS… ▸

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

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

    Comprehensible ▸ Actionable ▸ Targeted ▸ Reportable ▸ Is in place, timely and consistent ▸ Communicates errors only if necessary
  43. 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
  44. 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
  45. 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
  46. 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
  47. “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
  48. ✓ “offline” instead of “err.status === 0 || err.msg…” ✓

    Presets of default actions for common errors What we actually want
  49. ✓ “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
  50. 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);
  51. 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)
  52. 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)
  53. 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);
  54. Our strategy Setup a default ErrorHandler once Customise the default

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

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

    .default() .when(‘foo', onFoo) .when('bar', onBar) .handle(err);
  57. 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);
  58. A STORY OF ERRORS ErrorHandler .default() .when('offline', () => {

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

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

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

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

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

    of handling errors Readable domain related code Efficient error tracking Friction in development cycles
  64. 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
  65. 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