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. ERROR HANDLING
    MADE EASY JS
    VERSION

    View full-size slide

  2. Stratos Pavlakis
    Lead Engineer @Workable
    @th3hunt

    View full-size slide

  3. Error Handling

    View full-size slide

  4. Why bother?
    Cause good error handling builds TRUST 

    between our users and our product

    View full-size slide

  5. Why bother?
    Cause good error handling builds TRUST 

    between our users and our product
    It IS part of the UX

    View full-size slide

  6. Why bother?
    Cause good error handling builds TRUST 

    between our users and our product
    It IS part of the UX

    View full-size slide

  7. errors for developers

    View full-size slide

  8. NullPointerException
    Java

    View full-size slide

  9. RxCocoa.RxCocoaURLError
    Swift

    View full-size slide

  10. err
    Javascript

    View full-size slide

  11. InvalidLoginError
    Problem Domain

    View full-size slide

  12. errors for users?

    View full-size slide

  13. WHAT’S WRONG WITH THAT?

    View full-size slide

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

    View full-size slide

  15. Is that the case?

    View full-size slide

  16. Is that the case?
    Why do we keep getting this kind of 

    cryptic messages and bad UX?
    I asked at some point…

    View full-size slide

  17. WE DON’T DESIGN
    FOR ERROR

    View full-size slide

  18. Designing for errors

    View full-size slide

  19. DESIGNING FOR ERRORS
    what does that mean?
    Take #1

    View full-size slide

  20. DESIGNING FOR ERRORS
    MEANS…
    to anticipate errors!

    View full-size slide

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

    View full-size slide

  22. DESIGNING FOR ERRORS
    MEANS…
    ▸ List and identify them during design meetings
    ▸ Getting the product team involved
    to anticipate errors!

    View full-size slide

  23. – Younger self
    “I got a solid theory…
    Let’s do this!”

    View full-size slide

  24. “In theory there is no difference
    between theory and practice…

    View full-size slide

  25. – Jan L. A. van de Snepscheut
    “In theory there is no difference
    between theory and practice…
    In practice there is.”

    View full-size slide

  26. A story of errors

    View full-size slide

  27. A story of errors
    inspired from true events

    View full-size slide

  28. A STORY OF ERRORS
    WALTER

    View full-size slide

  29. A STORY OF ERRORS
    WALTER
    ▸ Senior Engineer

    View full-size slide

  30. A STORY OF ERRORS
    WALTER
    ▸ Senior Engineer
    ▸ Anticipates all errors.

    View full-size slide

  31. A STORY OF ERRORS
    INSTANT BUY

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. 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;

    View full-size slide

  42. 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;

    View full-size slide

  43. 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;
    }

    View full-size slide

  44. 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');
    }

    View full-size slide

  45. 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);

    View full-size slide

  46. A STORY OF ERRORS
    JESSE

    View full-size slide

  47. A STORY OF ERRORS
    JESSE
    ▸ Junior Engineer

    View full-size slide

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

    View full-size slide

  49. A STORY OF ERRORS
    JESSE
    ▸ Junior Engineer
    ▸ Hired 2 weeks ago
    ▸ Anticipates no errors. At all!

    View full-size slide

  50. A STORY OF ERRORS
    SAVE FOR LATER

    View full-size slide

  51. 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.

    View full-size slide

  52. A STORY OF ERRORS
    SAVE FOR LATER

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. 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

    View full-size slide

  56. 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

    View full-size slide

  57. 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

    View full-size slide

  58. 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

    View full-size slide

  59. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  64. 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;

    View full-size slide

  65. 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;
    }

    View full-size slide

  66. 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');
    }

    View full-size slide

  67. 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);

    View full-size slide

  68. 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);

    View full-size slide

  69. We end up with..

    View full-size slide

  70. We end up with..
    Boilerplate, Repetition

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  74. We end up with..
    Boilerplate, Repetition
    No standard way of handling errors
    Cognitive overhead
    Inefficient error reporting
    Friction in development cycles

    View full-size slide

  75. DESIGNING FOR ERRORS
    what does that mean?
    Take #2

    View full-size slide

  76. DESIGNING FOR ERRORS
    MEANS… (REVISED)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  79. DESIGNING FOR ERRORS
    MEANS… (REVISED)
    ▸ Anticipating errors
    ▸ Getting the product team involved
    ▸ Making error handling easy by engineering it

    View full-size slide

  80. DESIGNING FOR ERRORS
    checkpoint

    View full-size slide

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

    View full-size slide

  82. DESIGNING FOR ERRORS
    KINDS OF ERRORS

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  86. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  90. 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

    View full-size slide

  91. 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

    View full-size slide

  92. 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

    View full-size slide

  93. 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

    View full-size slide

  94. and we need it to be easy

    View full-size slide

  95. and we need it to be easy
    easy as in …

    View full-size slide

  96. Error handling
    as easy as ordering a burger

    View full-size slide

  97. lettuce
    bun
    tomato
    bacon
    cheddar
    onion
    patty

    View full-size slide

  98. “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

    View full-size slide

  99. Instead we say…
    “The usual…”
    or
    “Cowboy burger with extra
    bacon without the onions”

    View full-size slide

  100. What we actually want

    View full-size slide

  101. ✓ Natural language
    What we actually want

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  104. ✓ “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

    View full-size slide

  105. ErrorHandler
    A library that provides a
    declarative fluent API for flexible
    error handling

    View full-size slide

  106. 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);

    View full-size slide

  107. 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)

    View full-size slide

  108. ERROR HANDLER
    BASIC API
    handle(err, options)
    run(function)
    retry()

    View full-size slide

  109. 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)

    View full-size slide

  110. 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);

    View full-size slide

  111. Our strategy

    View full-size slide

  112. Our strategy
    Setup a default ErrorHandler once

    View full-size slide

  113. Our strategy
    Setup a default ErrorHandler once
    Customise the default ErrorHandler when
    needed based on the context

    View full-size slide

  114. Most of the time all we will need is…
    } catch (err) {
    }
    ErrorHandler.default().handle(err);

    View full-size slide

  115. and sometimes we need…
    } catch (err) {
    }
    ErrorHandler
    .default()
    .when(‘foo', onFoo)
    .when('bar', onBar)
    .handle(err);

    View full-size slide

  116. GANDALF APPROVES

    View full-size slide

  117. Back to our story

    View full-size slide

  118. 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);

    View full-size slide

  119. A STORY OF ERRORS
    ErrorHandler
    .default()
    .when('offline', () => {
    onlineTasks.push(() => saveForLater(item));
    })
    .handle(err);
    saveForLater(item)
    .catch(error => {
    });

    View full-size slide

  120. We get to..
    Boilerplate, Repetition
    No standard way of handling
    Cognitive overhead
    Inefficient error reporting
    Friction in development cycles

    View full-size slide

  121. We get to..
    No standard way of handling
    Cognitive overhead
    Inefficient error reporting
    Zero boilerplate, minimum repetition
    Friction in development cycles

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  125. 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

    View full-size slide

  126. – Swift Apprentice, Chapter 22 (Error Handling)
    “Error handling is the art of
    failing gracefully”

    View full-size slide

  127. 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

    View full-size slide