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

414db81dca21f468b4d7acd88172370c?s=128

Michele Bertoli

October 07, 2016
Tweet

Transcript

  1. Proper Error Handling @MicheleBertoli

  2. Michele (Mee-keh-leh) Front End Developer at YPlan Member of: •

    WEBdeBS + WEBdeLDN • React.js Italia Follow me @MicheleBertoli
  3. January 2017

  4. Poll Which talk would be more interesting?

  5. None
  6. None
  7. Errors are bad • Unhappy customers • Unhappy developers

  8. Let’s talk about errors

  9. JavaScript Debugging Normally, errors will happen, every time you try

    to write some new JavaScript code. source: w3schools.com
  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
  11. What is an error? • Incorrect result • Unintended behaviour

    • Inconsistent state
  12. Types of errors • Operational • Unexpected

  13. The problem • Your code will fail • The problem

    is not handling the errors • Users find the bugs for you
  14. Error handling • Give a feedback to the users •

    Discover errors quickly • Store informations to reproduce problems
  15. Requirements

  16. Redux ACTIONS REDUCERS COMPONENTS

  17. Sagas ACTIONS REDUCERS COMPONENTS EFFECTS

  18. Webpack • Module bundler • Code splitting • Loaders •

    Plugin System
  19. Common error handling solutions

  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)
  21. Browser message source lineno colno error Firefox √ √ √

    √ √ Chrome √ √ √ √ √ Edge √ √ √ √ IE11 √ √ √ √ √ Safari √ √ √ √
  22. Error Object message*: Human-readable description fileName*: Name of the file

    lineNumber*: Line number * Optional
  23. Different origin • Script error • “crossorigin” attribute • CORS

    HTTP response headers
  24. The best error handler ever window.onerror = message => (

    window.location.href = ( `http://stackoverflow.com/search?q=[js]${message}` ) )
  25. Stack Trace (or it didn’t happen)

  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)
  27. Error.prototype.stack • Non-standard property • Different formats

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

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

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

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

    web browsers.
  32. TraceKit Attempts to create stack traces for unhandled JavaScript exceptions

    in all major browsers.
  33. try...catch

  34. Definition The try...catch statement marks a block of statements to

    try, and specifies a response, should an exception be thrown.
  35. Usage try { } catch (e) { }

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

  37. Performances const errorObject = { value: null } function tryCatch(fn,

    ctx, args) { try { return fn.apply(ctx, args) } catch (e) { errorObject.value = e return errorObject } }
  38. ryanmorr/try-catch tryCatch(() => { // try something }, error =>

    { // handle error })
  39. Custom error handling solutions

  40. Why? • Collect more informations • Make debugging easier •

    Specialised solutions
  41. MicheleBertoli/proper-error-handling • Operational • Components • Actions • Reducers •

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

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

    }
  44. Formatting the stack trace const log = stack => {

    console.log( stack.map(frame => frame.toString()).join('\n') ) console.groupEnd() }
  45. Webpack devtool • eval • source-map

  46. Global handler window.onerror = (msg, src, lineno, colno, err) =>

    { Logger.error(err, { msg, src, lineno, colno }) return true }
  47. Module error import ThrowingModule from 'utils/throwing-module' window.onerror = ...

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

  49. Operational errors handling

  50. Flow Errors Watcher API_GET Watcher API_GET Action API_GET_FAILUR E Action

    Logger HTTP Client
  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) }
  52. Notifications const filter = action => action.error function* takeEveryError() {

    yield* takeEvery(filter, errorHandler) } function* errorHandler({ error, type }) { yield put(NotificationActions.send(error, type)) }
  53. Components

  54. Example const Nice = () => <div>Nice</div> const Evil =

    () => ( <div> Evil {this.does.not.exist} </div> )
  55. What happens? const Main = () => ( <div> <Nice

    /> <Evil /> <Nice /> </div> )
  56. Errors in components • A single broken component stops the

    rendering • Errors don’t bubble up
  57. facebook/react #2461 • Open from Nov 2014 • Suggested workaround

    “Monkey patching” • Please go and add a +1
  58. staxmanade/react-component-errors import wrap from 'react-component-errors' class MyComponent extends React.Component {

    ... } wrap(MyComponent)
  59. Config import { config } from 'react-component-errors' config.errorHandler = errorReport

    => ( Logger.error(errorReport.error, errorReport) )
  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
  61. MicheleBertoli/react-poop import poop from 'react-poop' class MyComponent extends React.Component {

    ... } export default poop(MyComponent)
  62. Features • Wraps the render method into a try...catch block

    • Works with stateless functional components • Nonsense
  63. Actions

  64. Custom bindActionCreators const errorHandler = (error, action) => ( Logger.error(error,

    action) ) const mapDispatchToProps = dispatch => ({ apiActions: safeBindActionCreators( ApiActions, dispatch, errorHandler ), })
  65. Features • Wraps the bound actions into a try...catch block

    • Catches all the synchronous errors • Passes error and action name to the handler
  66. Reducers

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

    => ( Logger.error(error, getState()) ) applyMiddleware( reduxCatch(errorHandler) )
  68. Features • Wraps the reducers into a try...catch block •

    Configurable error handler • Passes “getState” to the handler
  69. Redux DevTools extension • Chrome, Firefox and Electron • Import/export

    state
  70. Sagas

  71. Uncaught uncaught at root at takeEveryThrowErrorInSagas at throwError

  72. try...catch function* throwError() { try { yield call( () =>

    { throw new Error('Saga error') } ) } catch (error) { yield call(Logger.error, error) } }
  73. Selectors

  74. Custom mapStateToProps const errorHandler = (error, { state, ownProps })

    => ( Logger.error(error, { state, ownProps }) ) const mapStateToProps = safeMapStateToProps( state => ({ shouldThrow: getShouldThrow(state) }), errorHandler )
  75. Features • Wraps the selectors into a try...catch block •

    Passes error, state and props to the handler
  76. reactjs/react-redux const haveStatePropsChanged = tryCatch( this.updateStatePropsIfNeeded, this ) ... if

    (haveStatePropsChanged === errorObject) { this.statePropsPrecalculationError = ( errorObject.value ) }
  77. reactjs/react-redux render() { ... if (statePropsPrecalculationError) { throw statePropsPrecalculationError }

    ... }
  78. Bonus

  79. Debugging ?secret_debugging_flag=true

  80. Erdux “Unpredictable state container for JavaScript apps” Redux-inspired state container

    based on Error Objects and the Global handler.
  81. Redux Erdux Dispatch Actions Errors Update Subscription window.onerror State management

    Reducers Reducers Useful Yes No
  82. Erdux class IncrementAction extends Error {} const increment = document.getElementById('increment')

    increment.addEventListener( 'click', () => { throw new IncrementAction() } )
  83. Erdux const reducer = (state, error) => { switch (error.constructor)

    { case IncrementAction: return state + 1 default: return state } }
  84. Approved

  85. Recap

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

    NOPE!
  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)
  88. Any questions?