Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

To Err Is Human

3,372 views

Published on

Building complex async applications is really hard. Whether you use callbacks, Promises, or EventEmitters, Error objects should have a place in your utility belt. They are indispensable when it comes to managing work flows in a highly asynchronous environment.

This talk covers patterns for using JavaScript Error (with a capital E) objects to build resilient applications, and introduce some modules that can be used to build errors with an elegant history of stack traces even through multiple asynchronous operations. Try/catch, callbacks, and other error handling mechanisms will be examined, revealing some potential deficiencies in the JavaScript language for dealing with errors.

Video: https://www.youtube.com/watch?v=PyCHbi_EqPs

Published in: Software
  • Be the first to comment

To Err Is Human

  1. 1. To Err Is Human Alex Liu @stinkydofu aliu@netflix.com
  2. 2. ☐ avoidance ☐ acceptance
  3. 3. ☐ avoidance ☐ acceptance☑
  4. 4. The Road To Happiness ‣ Handling Errors and Exceptions ‣ Preserving History ‣ The Problem With Catch
  5. 5. Handling Errors and Exceptions 1
  6. 6. Error > new Error();
  7. 7. kinds of errors two
  8. 8. Expected
  9. 9. Expected
  10. 10. Expected ‣ invalid user input ‣ bad JSON input ‣ file not found ‣ request timeout ‣ request 500
  11. 11. Unexpected
  12. 12. Unexpected
  13. 13. Unexpected ‣ programmer typos ‣ read property of undefined ‣ undefined is not a function
  14. 14. How do I communicate expected errors?
  15. 15. Exception > throw new Error();
  16. 16. an exception is a thrown error
  17. 17. You could throw anything > throw { foo: bar }; > throw 0; > throw 'my error';
  18. 18. > throw 0; undefined > throw 'something happened'; something happened > throw new Error('something happened'); Error: something happened at repl:1:7 at REPLServer.defaultEval (repl.js:132:27) at bound (domain.js:254:14) at REPLServer.runBound [as eval] (domain.js:267:12) at REPLServer.<anonymous> (repl.js:279:12) at REPLServer.emit (events.js:107:17) at REPLServer.Interface._onLine (readline.js:214:10) at REPLServer.Interface._line (readline.js:553:8) at REPLServer.Interface._ttyWrite (readline.js:830:14)
  19. 19. index.js
  20. 20. index.js data.js get.js
  21. 21. index.js data.js get.js throw!
  22. 22. index.js data.js get.js throw! catch?
  23. 23. index.js data.js get.js throw!catch?
  24. 24. index.js data.js get.js throw!catch?
  25. 25. catch (e) { // run! } index.js data.js get.js throw!
  26. 26. CRASH! throw! index.js data.js get.js
  27. 27. throw / try / catch try { var price = getStockPrice('NFLX'); document.write('NFLX: ' + price); } catch(err) { // err instanceof Error => true document.write('NFLX: unavailable'); }
  28. 28. Can we catch? try { getStockPrice('NFLX', function(err) { if (err) { throw (err); } /* continue as normal */
 }); } catch(err) { /* error handling */ }
  29. 29. Error first callbacks (errbacks) getStockPrice('NFLX', function(err, price) { if (err) { /* handle err! */ } document.write('NFLX: ' + price); });
  30. 30. Error first callbacks (errbacks) getStockPrice('NFLX', function(err, price) { if (err) { /* handle err! */ } document.write('NFLX: ' + price); });
  31. 31. Error first callbacks (errbacks) getStockPrice('NFLX', function(err, price) { if (err) { document.write('NFLX: unavailable'); return; } document.write('NFLX: ' + price); });
  32. 32. Bubble up if you can’t handle it function drawStockPrice(callback) { getStockPrice('NFLX', function(err, price) { return callback(err); }); }
  33. 33. index.js
  34. 34. index.js data.js get.js cb(err)
  35. 35. index.js data.js get.js cb(err) cb(err)
  36. 36. if (err) { ... } index.js data.js get.js cb(err) cb(err)
  37. 37. SWALLOWED! cb(err)cb(err) index.js data.js get.js
  38. 38. errbacks are a channel for expected errors
  39. 39. > throw err; > callback(err); sync async
  40. 40. nope nope nope nope nope nope nopenope nope nopenope nope nope nope nope nope nope
  41. 41. throw / try / catch
 when in async env sanely you can’t
  42. 42. favor errbacks because of consistency
  43. 43. How do I communicate unexpected errors?
  44. 44. unexpected errors are thrown automatically
  45. 45. process.on('uncaughtException', function(err) { ... }); Catch ‘em all, even async
  46. 46. process.on('uncaughtException', function(err) { ... }); Catch ‘em all, even async window.onerror = function(err) { ... });
  47. 47. process.on('uncaughtException', function(err) { ... }); Catch ‘em all, even async window.onerror = function(err) { ... }); _BAD IDEA_
  48. 48. Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } }
  49. 49. Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } } <----------------------------------- (1)
  50. 50. Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } } <----------------------------------- (1) <------------------------------- (2)
  51. 51. Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } } <----------------------------------- (1) <------------------------------- (2) <-------------------------------------------------------- (3)
  52. 52. Catch and do what…? for (var i = 0; i < doThings.length; i++) { try { doThings[i](); } catch (e) { ... } }
  53. 53. Catch and do what…? for (var i = 0; i < doThings.length; i++) { try { doThings[i](); } catch (e) { ... } } <--------------------- what goes here?
  54. 54. programmer err : rollback ::
  55. 55. programmer err : rollback :: rollback err : ?
  56. 56. you can’t fix unexpected errors because their existence is unexpected
  57. 57. in node.js, abort
 on unexpected errors
  58. 58. goal is to 
 the blast radius (not to write bug free code) minimize
  59. 59. continuing after an unhandled exception is a trade off
  60. 60. _BROKEN_
  61. 61. program error : program crash ::
  62. 62. program error : program crash :: OS error : ?
  63. 63. program error : program crash :: OS error : OS crash
  64. 64. in the browser, report
 all unexpected errors
  65. 65. avoid
 handling errors in global exception handlers
  66. 66. Preserving History 2
  67. 67. Working backward… function getEpicStory(cb) { database.get('iliad', function(err, data) { if (err) { return cb(err); } return cb(null, data); }); }
  68. 68. We can do better database.get('iliad', function(err, data) { if (err) { err.cause = 'db error!'; err.timestamp = new Date(); return cb(err); } return cb(null, data); });
  69. 69. VError: we can do even better database.get('iliad', function(err, data) { if (err) { return cb(new VError(err, 'db error!')); } return cb(null, data); });
  70. 70. VError: we can do even better // redis error [Error: missing key] // our verror { [VError: db error: missing key] jse_shortmsg: 'db error', jse_summary: 'db error: missing key', jse_cause: [Error: missing key], message: 'db error: missing key' }
  71. 71. [2015-10-02T06:41:54.389Z] ERROR: test/11497 on lgml-aliu: db error: missing key VError: db error: missing key at Object.<anonymous> (/Users/aliu/Desktop/test/a.js:6:12) at Module._compile (module.js:434:26) at Object.Module._extensions..js (module.js:452:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:475:10) at startup (node.js:117:18) at node.js:951:3 Caused by: Error: missing key at Object.<anonymous> (/Users/aliu/Desktop/test/a.js:5:12) at Module._compile (module.js:434:26) at Object.Module._extensions..js (module.js:452:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:475:10) at startup (node.js:117:18) at node.js:951:3
  72. 72. restify-errors: the bees’ knees var errs = require('restify-errors'); errs.makeConstructor('DatabaseError', { fooProp: 'someVal' }); var myErr = new errs.DatabaseError('db error!'); // myErr.fooProp => someVal
  73. 73. restify-errors: the bees’ knees var myErr = new errs.DatabaseError('db error!'); myErr instanceof errs.DatabaseError // => true myErr instanceof VError // => true myErr instanceof Error // => true
  74. 74. Serving out the UI request('/api/story', function(err, fpStoryData) { if (err instanceof HttpError) { return res.render('NetworkErrorPage'); } else if (err instanceof DatabaseError) { return res.render('UnavailablePage'); } else { return res.render('ErrorPage'); } res.render('iliad', fpStoryData); });
  75. 75. [2015-10-06T17:50:31.561Z] ERROR: test/64987 on lgml-aliu: rendering error! RenderError: rendering error page! at Object.<anonymous> (/Users/aliu/Desktop/test/test4.js:10:10) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 Caused by: DataValidationError: data validation error at Object.<anonymous> (/Users/aliu/Desktop/test/test4.js:10:32) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 Caused by: RequestError: request error at Object.<anonymous> (/Users/aliu/Desktop/test/test4.js:10:63) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 Caused by: BadRequestError: http 400 at Object.<anonymous> (/Users/aliu/Desktop/test/test4.js:10:86) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10)
  76. 76. [2015-10-06] ERROR: test/64987 on lgml-aliu: rendering error!
 Error: something happened at repl:1:7 at REPLServer.defaultEval (repl.js:132:27) at bound (domain.js:254:14) at REPLServer.runBound [as eval] (domain.js:267:12) at REPLServer.<anonymous> (repl.js:279:12) at REPLServer.emit (events.js:107:17) at REPLServer.Interface._onLine (readline.js:214:10) at REPLServer.Interface._line (readline.js:553:8) at REPLServer.Interface._ttyWrite (readline.js:830:14) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3
  77. 77. use 
 for expected errors typed errors
  78. 78. 3 The problem with catch
  79. 79. But {abstraction} solved this already!?
  80. 80. try: catch ‘em all try { if (Math.random() > 0.5) { JSON.parse(input); } else { var x = y + 1; } } catch (err) {...}
  81. 81. > [SyntaxError: Unexpected token b] > [ReferenceError: y is not defined] expected: unexpected: JSON.parse(input); var x = y + 1;
  82. 82. Catching typed errors catch (err) { if (err instanceof SyntaxError) { ... } }
  83. 83. Don’t forget to rethrow! catch (err) { if (err instanceof SyntaxError) { ... } else { throw err; } }
  84. 84. Centralize throwing catch (err) { throwUnexpected(err); // handle err if not thrown }
  85. 85. expected vs unexpected…? catch (err) { if (err instanceof SyntaxError) { ... } else { throw err; } }
  86. 86. expected vs unexpected…? > JSON.parse('bad input'); > vart x;
  87. 87. try/catch cannot differentiate source of errors
  88. 88. try: reduce surface area try { JSON.parse(input); } catch (err) { ... } var x = y + 1; <------------------- throws
  89. 89. be very targeted with try / catch
  90. 90. promises: catch ‘em all 
 foo.then(foo2) .then(foo3) .catch(function(err) { ... });

  91. 91. promises: unexpected behavior 
 
 
 
 
 
 
 
 
 

 
 
 foo.then(foo2) .then(fooError) .catch(function(err) { if (!(err instanceof ExpectedErr)) { throw err; } });
  92. 92. $ node promiseDemo.js Unhandled rejection ReferenceError: y is not defined at foo (/test/promiseDemo.js:14:13) at /test/promiseDemo.js:8:3 at processImmediate [as _immediateCallback] (timers.js:368:17) From previous event: at Object.<anonymous> (/test/promiseDemo.js:7:3) at Module._compile (module.js:435:26) at Object.Module._extensions..js (module.js:442:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:311:12) at Function.Module.runMain (module.js:467:10) at startup (node.js:134:18) at node.js:961:3
  93. 93. $
  94. 94. $ echo $?
  95. 95. $ echo $? 0
  96. 96. promises: unexpected behavior 
 
 
 
 
 
 
 
 
 

 
 
 foo.then(foo2) .then(fooError) .catch(function(err) { if (!(err instanceof ExpectedErr)) { throw err; } });
  97. 97. promises: call done() foo.then(foo2) .then(fooError) .catch(function(err) { if (!(err instanceof ExpectedErr)) { throw err; } }) .done();
  98. 98. bluebird: catch only expected 
 
 
 
 
 
 foo.then(foo2) .then(fooError) .catch(ExpectedError, function(err) { ... }); // EVERYTHING else throws, exits 1!
  99. 99. bluebird: throw by default Promise.onPossiblyUnhandledRejection(function(e) { throw e; });
  100. 100. surfacing unexpected errors should NOT be opt-in
  101. 101. Making Catch Better You don't need to sell me -- we had } catch (e if ...) { in SpiderMonkey and proposed it for ES3 (in 1998). - Brendan Eich try { ... } catch (e if ...) { ... }
  102. 102. Refutable Pattern Matching GuardedPattern(refutable) ::= Pattern(refutable) "if" AssignmentExpression
  103. 103. Looking Forward
  104. 104. for rich error objects 5 npm install verror npm install restify-errors
  105. 105. Handle every error from every async API 4
  106. 106. Understand how your abstractions handle errors 3 (swallow? rethrow?)
  107. 107. Abort immediately on unexpected errors to minimize the blast radius 2
  108. 108. Design and build applications with error handling in mind 1 (exceptions are NOT exceptional!)
  109. 109. Error handling bible ‣ https://www.joyent.com/developers/node/ design/errors
  110. 110. Aborting on unexpected errors ‣ https://github.com/nodejs/node-v0.x-archive/ issues/5114 ‣ https://github.com/nodejs/node-v0.x-archive/ issues/5149
  111. 111. Module up! ‣ verror ‣ https://github.com/davepacheco/node-verror ‣ restify-errors ‣ https://github.com/restify/errors
  112. 112. Get involved! ‣ https://esdiscuss.org/topic/try-catch-conditional- exceptions-in-light-of-generators ‣ http://wiki.ecmascript.org/doku.php? id=strawman:pattern_matching
  113. 113. Fin Alex Liu @stinkydofu aliu@netflix.com
  114. 114. Image Credits
  115. 115. Image Credits
  116. 116. Image Credits
  117. 117. Image Credits ‣ http://lizclimo.tumblr.com/post/77531229510/youre-doing-it-wrong ‣ http://fantasio.deviantart.com/art/Godzilla-in-the-mountains-454304871 ‣ http://rockpapercynic.tumblr.com/post/85581052929/first-revealed-pagew-your-bad- idea-illustration

×