$30 off During Our Annual Pro Sale. View Details »

Proper Error Handling

Proper Error Handling

No matter how good you are as a developer and how many tests you write: your application will throw errors.
At YPlan we tried to catch all the exceptions and handle them in the right way to give our users the best possible experience.
In this talk, I will go through the problems we tried to solve and the solutions we implemented #forreal

Michele Bertoli

October 07, 2016
Tweet

More Decks by Michele Bertoli

Other Decks in Programming

Transcript

  1. Proper Error Handling
    @MicheleBertoli

    View Slide

  2. Michele (Mee-keh-leh)
    Front End Developer at YPlan
    Member of:
    ● WEBdeBS + WEBdeLDN
    ● React.js Italia
    Follow me @MicheleBertoli

    View Slide

  3. January 2017

    View Slide

  4. Poll
    Which talk would be more interesting?

    View Slide

  5. View Slide

  6. View Slide

  7. Errors are bad
    ● Unhappy customers
    ● Unhappy developers

    View Slide

  8. Let’s talk about errors

    View Slide

  9. JavaScript Debugging
    Normally, errors will happen, every time you try to
    write some new JavaScript code.
    source: w3schools.com

    View Slide

  10. Cost of errors
    ● Bugs cost economy $312 billion per year
    ● Developers spend 50% of their programming time
    finding and fixing bugs
    source: Cambridge University

    View Slide

  11. What is an error?
    ● Incorrect result
    ● Unintended behaviour
    ● Inconsistent state

    View Slide

  12. Types of errors
    ● Operational
    ● Unexpected

    View Slide

  13. The problem
    ● Your code will fail
    ● The problem is not handling the errors
    ● Users find the bugs for you

    View Slide

  14. Error handling
    ● Give a feedback to the users
    ● Discover errors quickly
    ● Store informations to reproduce problems

    View Slide

  15. Requirements

    View Slide

  16. Redux
    ACTIONS REDUCERS
    COMPONENTS

    View Slide

  17. Sagas
    ACTIONS REDUCERS
    COMPONENTS
    EFFECTS

    View Slide

  18. Webpack
    ● Module bundler
    ● Code splitting
    ● Loaders
    ● Plugin System

    View Slide

  19. Common error handling solutions

    View Slide

  20. window.onerror
    message: Error message (string)
    source: URL of the script (string)
    lineno: Line number (number)
    colno: Column number for the line (number)
    error: Error Object (object)

    View Slide

  21. Browser message source lineno colno error
    Firefox √ √ √ √ √
    Chrome √ √ √ √ √
    Edge √ √ √ √
    IE11 √ √ √ √ √
    Safari √ √ √ √

    View Slide

  22. Error Object
    message*: Human-readable description
    fileName*: Name of the file
    lineNumber*: Line number
    * Optional

    View Slide

  23. Different origin
    ● Script error
    ● “crossorigin” attribute
    ● CORS HTTP response headers

    View Slide

  24. The best error handler ever
    window.onerror = message => (
    window.location.href = (
    `http://stackoverflow.com/search?q=[js]${message}`
    )
    )

    View Slide

  25. Stack Trace (or it didn’t happen)

    View Slide

  26. Example
    Error: Component error
    at ReactComponentErrors.throwError
    (http://localhost:8080/bundle.js:27522:16)
    at ReactComponentErrors.componentWillReceiveProps
    (http://localhost:8080/bundle.js:27516:18)
    at ReactComponentErrors.component.(anonymous function)
    (http://localhost:8080/bundle.js:27591:22)
    at ReactCompositeComponentWrapper.updateComponent
    (http://localhost:8080/bundle.js:19709:13)

    View Slide

  27. Error.prototype.stack
    ● Non-standard property
    ● Different formats

    View Slide

  28. console.log(new Error().stack)
    Error
    at :1:13
    Chrome

    View Slide

  29. console.log(new Error().stack)
    @debugger eval code:1:13
    FireFox

    View Slide

  30. console.log(new Error().stack)
    eval code
    eval@[native code]
    _evaluateOn
    _evaluateAndWrap
    evaluate
    Safari

    View Slide

  31. StackTrace.JS
    Generate, parse, and enhance JavaScript stack traces
    in all web browsers.

    View Slide

  32. TraceKit
    Attempts to create stack traces for unhandled
    JavaScript exceptions in all major browsers.

    View Slide

  33. try...catch

    View Slide

  34. Definition
    The try...catch statement marks a block of statements
    to try, and specifies a response, should an exception
    be thrown.

    View Slide

  35. Usage
    try {
    } catch (e) {
    }

    View Slide

  36. Re-throw
    try {
    } catch (e) {
    throw e
    }

    View Slide

  37. Performances
    const errorObject = { value: null }
    function tryCatch(fn, ctx, args) {
    try {
    return fn.apply(ctx, args)
    } catch (e) {
    errorObject.value = e
    return errorObject
    }
    }

    View Slide

  38. ryanmorr/try-catch
    tryCatch(() => {
    // try something
    }, error => {
    // handle error
    })

    View Slide

  39. Custom error handling solutions

    View Slide

  40. Why?
    ● Collect more informations
    ● Make debugging easier
    ● Specialised solutions

    View Slide

  41. MicheleBertoli/proper-error-handling
    ● Operational
    ● Components
    ● Actions
    ● Reducers
    ● Sagas
    ● Selectors

    View Slide

  42. Logging info
    info(message, extra) {
    console.group('ℹ ')
    console.log(message)
    console.log(extra)
    console.groupEnd()
    },

    View Slide

  43. Logging errors
    error(error, extra) {
    console.group(' ')
    console.log(error)
    console.log(extra)
    StackTrace.fromError(error).then(log)
    }

    View Slide

  44. Formatting the stack trace
    const log = stack => {
    console.log(
    stack.map(frame => frame.toString()).join('\n')
    )
    console.groupEnd()
    }

    View Slide

  45. Webpack devtool
    ● eval
    ● source-map

    View Slide

  46. Global handler
    window.onerror = (msg, src, lineno, colno, err) => {
    Logger.error(err, { msg, src, lineno, colno })
    return true
    }

    View Slide

  47. Module error
    import ThrowingModule from 'utils/throwing-module'
    window.onerror = ...

    View Slide

  48. Multiple entries
    entry: [
    './src/utils/onerror',
    './src',
    ],

    View Slide

  49. Operational errors handling

    View Slide

  50. Flow
    Errors Watcher
    API_GET Watcher
    API_GET Action
    API_GET_FAILUR
    E Action
    Logger
    HTTP Client

    View Slide

  51. Catch all Saga
    const filter = action => action.error
    function* takeEveryError() {
    yield* takeEvery(filter, errorHandler)
    }
    function* errorHandler({ error, type }) {
    yield call(Logger.info, error, type)
    }

    View Slide

  52. Notifications
    const filter = action => action.error
    function* takeEveryError() {
    yield* takeEvery(filter, errorHandler)
    }
    function* errorHandler({ error, type }) {
    yield put(NotificationActions.send(error, type))
    }

    View Slide

  53. Components

    View Slide

  54. Example
    const Nice = () => Nice
    const Evil = () => (

    Evil
    {this.does.not.exist}

    )

    View Slide

  55. What happens?
    const Main = () => (





    )

    View Slide

  56. Errors in components
    ● A single broken component stops the rendering
    ● Errors don’t bubble up

    View Slide

  57. facebook/react #2461
    ● Open from Nov 2014
    ● Suggested workaround “Monkey patching”
    ● Please go and add a +1

    View Slide

  58. staxmanade/react-component-errors
    import wrap from 'react-component-errors'
    class MyComponent extends React.Component {
    ...
    }
    wrap(MyComponent)

    View Slide

  59. Config
    import { config } from 'react-component-errors'
    config.errorHandler = errorReport => (
    Logger.error(errorReport.error, errorReport)
    )

    View Slide

  60. Features
    ● Wraps the lifecycle methods into a try...catch block
    ● Configurable error handler
    ● Passes component name, method, props and Error
    Object to the handler

    View Slide

  61. MicheleBertoli/react-poop
    import poop from 'react-poop'
    class MyComponent extends React.Component {
    ...
    }
    export default poop(MyComponent)

    View Slide

  62. Features
    ● Wraps the render method into a try...catch block
    ● Works with stateless functional components
    ● Nonsense

    View Slide

  63. Actions

    View Slide

  64. Custom bindActionCreators
    const errorHandler = (error, action) => (
    Logger.error(error, action)
    )
    const mapDispatchToProps = dispatch => ({
    apiActions: safeBindActionCreators(
    ApiActions, dispatch, errorHandler
    ),
    })

    View Slide

  65. Features
    ● Wraps the bound actions into a try...catch block
    ● Catches all the synchronous errors
    ● Passes error and action name to the handler

    View Slide

  66. Reducers

    View Slide

  67. sergiodxa/redux-catch
    import reduxCatch from 'redux-catch'
    const errorHandler = (error, getState) => (
    Logger.error(error, getState())
    )
    applyMiddleware(
    reduxCatch(errorHandler)
    )

    View Slide

  68. Features
    ● Wraps the reducers into a try...catch block
    ● Configurable error handler
    ● Passes “getState” to the handler

    View Slide

  69. Redux DevTools extension
    ● Chrome, Firefox and Electron
    ● Import/export state

    View Slide

  70. Sagas

    View Slide

  71. Uncaught
    uncaught at root
    at takeEveryThrowErrorInSagas
    at throwError

    View Slide

  72. try...catch
    function* throwError() {
    try {
    yield call(
    () => { throw new Error('Saga error') }
    )
    } catch (error) {
    yield call(Logger.error, error)
    }
    }

    View Slide

  73. Selectors

    View Slide

  74. Custom mapStateToProps
    const errorHandler = (error, { state, ownProps }) => (
    Logger.error(error, { state, ownProps })
    )
    const mapStateToProps = safeMapStateToProps(
    state => ({ shouldThrow: getShouldThrow(state) }),
    errorHandler
    )

    View Slide

  75. Features
    ● Wraps the selectors into a try...catch block
    ● Passes error, state and props to the handler

    View Slide

  76. reactjs/react-redux
    const haveStatePropsChanged = tryCatch(
    this.updateStatePropsIfNeeded,
    this
    )
    ...
    if (haveStatePropsChanged === errorObject) {
    this.statePropsPrecalculationError = (
    errorObject.value
    )
    }

    View Slide

  77. reactjs/react-redux
    render() {
    ...
    if (statePropsPrecalculationError) {
    throw statePropsPrecalculationError
    }
    ...
    }

    View Slide

  78. Bonus

    View Slide

  79. Debugging
    ?secret_debugging_flag=true

    View Slide

  80. Erdux
    “Unpredictable state container for JavaScript apps”
    Redux-inspired state container based on Error
    Objects and the Global handler.

    View Slide

  81. Redux Erdux
    Dispatch Actions Errors
    Update Subscription window.onerror
    State management Reducers Reducers
    Useful Yes No

    View Slide

  82. Erdux
    class IncrementAction extends Error {}
    const increment = document.getElementById('increment')
    increment.addEventListener(
    'click',
    () => { throw new IncrementAction() }
    )

    View Slide

  83. Erdux
    const reducer = (state, error) => {
    switch (error.constructor) {
    case IncrementAction:
    return state + 1
    default:
    return state
    }
    }

    View Slide

  84. Approved

    View Slide

  85. Recap

    View Slide

  86. FAQ
    Are you telling me to put try...catch blocks
    everywhere? NOPE!

    View Slide

  87. Proper Error Handling
    ● Your code will fail
    ● Find the right balance between performance and
    error handling
    ● Think about the users first
    ● Never stop experimenting (and sharing)

    View Slide

  88. Any questions?

    View Slide