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 Slide

  2. Stratos Pavlakis
    Lead Engineer @Workable
    @th3hunt

    View Slide

  3. Error Handling

    View Slide

  4. Why bother?

    View Slide

  5. Why bother?
    Cause good error handling builds TRUST 

    between our users and our product

    View Slide

  6. Why bother?
    Cause good error handling builds TRUST 

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

    View Slide

  7. Why bother?
    Cause good error handling builds TRUST 

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

    View Slide

  8. errors for developers

    View Slide

  9. NullPointerException
    Java

    View Slide

  10. RxCocoa.RxCocoaURLError
    Swift

    View Slide

  11. e
    Javascript

    View Slide

  12. err
    Javascript

    View Slide

  13. InvalidLoginError
    Problem Domain

    View Slide

  14. errors for users?

    View Slide

  15. View Slide

  16. WHAT’S WRONG WITH THAT?

    View Slide

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

    View Slide

  18. Is that the case?

    View Slide

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

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

    View Slide

  20. View Slide

  21. WE DON’T DESIGN
    FOR ERROR

    View Slide

  22. Designing for errors

    View Slide

  23. View Slide

  24. View Slide

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

    View Slide

  26. DESIGNING FOR ERRORS
    MEANS…
    to anticipate errors!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. A story of errors

    View Slide

  33. A story of errors
    inspired from true events

    View Slide

  34. A STORY OF ERRORS
    WALTER

    View Slide

  35. A STORY OF ERRORS
    WALTER
    ▸ Senior Engineer

    View Slide

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

    View Slide

  37. A STORY OF ERRORS
    INSTANT BUY

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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;

    View Slide

  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;

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. A STORY OF ERRORS
    JESSE

    View Slide

  53. A STORY OF ERRORS
    JESSE
    ▸ Junior Engineer

    View Slide

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

    View Slide

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

    View Slide

  56. A STORY OF ERRORS
    SAVE FOR LATER

    View Slide

  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.

    View Slide

  58. A STORY OF ERRORS
    SAVE FOR LATER

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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;

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  75. View Slide

  76. View Slide

  77. View Slide

  78. View Slide

  79. View Slide

  80. We end up with..

    View Slide

  81. We end up with..
    Boilerplate, Repetition

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  86. View Slide

  87. View Slide

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

    View Slide

  89. DESIGNING FOR ERRORS
    MEANS… (REVISED)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  93. DESIGNING FOR ERRORS
    checkpoint

    View Slide

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

    View Slide

  95. DESIGNING FOR ERRORS
    KINDS OF ERRORS

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  107. and we need it to be easy

    View Slide

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

    View Slide

  109. Error handling
    as easy as ordering a burger

    View Slide

  110. lettuce
    bun
    tomato
    bacon
    cheddar
    onion
    patty

    View Slide

  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

    View Slide

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

    View Slide

  113. What we actually want

    View Slide

  114. ✓ Natural language
    What we actually want

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

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

    View Slide

  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)

    View Slide

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

    View Slide

  124. Our strategy

    View Slide

  125. Our strategy
    Setup a default ErrorHandler once

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  129. View Slide

  130. GANDALF APPROVES

    View Slide

  131. Back to our story

    View Slide

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

    View Slide

  133. turns to

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide