PageRenderTime 44ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/_posts/2014-01-30-exception-error.md

http://github.com/alexyoung/dailyjs
Markdown | 106 lines | 73 code | 33 blank | 0 comment | 0 complexity | e7911bbcfe1c392216df19a66e4980cd MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. ---
  2. layout: post
  3. title: "The Art of Error"
  4. author: Alex Young
  5. categories:
  6. - node
  7. - errors
  8. ---
  9. <div class="image">
  10. <img src="/images/posts/iamerror.png" />
  11. <small>Error was originally a character in the hit video game, "Zelda".</small>
  12. </div>
  13. I like to define a lot of objects that inherit from `Error`. I find it helps me to track down issues -- post-mortem -- but also to clearly handle expected errors. Sometimes writing error handling code feels like a chore, but it shouldn't be an afterthought. Well-designed and well-tested errors will help you maintain projects, but also help users figure out what to do when things go wrong.
  14. When it comes to using `Error`, I've found two bad practices that should be avoided:
  15. 1. `new Error` is used instead of a subclass.
  16. 2. `Error` is avoided altogether because "exceptions are bad".
  17. Let's look at how to avoid these issues and use errors properly.
  18. ###Subclassing Error
  19. Subclassing errors is easy with `Object.create` or `util.inherits` (in Node). Here's how you do it in Node:
  20. {% highlight javascript %}
  21. var assert = require('assert');
  22. var util = require('util');
  23. function NotFound(message) {
  24. Error.call(this);
  25. this.message = message;
  26. }
  27. util.inherits(NotFound, Error);
  28. var error = new NotFound('/bitcoin-wallet not found');
  29. assert(error.message);
  30. assert(error instanceof NotFound);
  31. assert(error instanceof Error);
  32. assert.equal(error instanceof RangeError, false);
  33. {% endhighlight %}
  34. The assertions check that the expected property was set (`message`), and `error` is an instance of `NotFound`, `Error`, but _not_ `RangeError`.
  35. If you were using this with [Express](http://expressjs.com/), you could set other properties to make the error more useful. This is great when passing errors to `next()` in routes. When dealing with errors at the HTTP layer, I like to include a status code:
  36. {% highlight javascript %}
  37. function NotFound(message) {
  38. Error.call(this);
  39. this.message = message;
  40. this.statusCode = 404;
  41. }
  42. {% endhighlight %}
  43. Now you could have error handling middleware that handles errors in a more DRY fashion:
  44. {% highlight javascript %}
  45. app.use(function(err, req, res, next) {
  46. console.error(err.stack);
  47. if (!err.statusCode || err.statusCode === 500) {
  48. emails.error({ err: err, req: req });
  49. }
  50. res.send(err.statusCode || 500, err.message);
  51. });
  52. {% endhighlight %}
  53. This will send the HTTP status code to the browser, if available. It also only emails errors when the `statusCode` is 500 or not set. I took this from production code that generates emails when unusual things happen, and I don't want to get notified about general errors like 401, 403, and 404.
  54. The line that reads `console.error(err.stack)` won't actually work as expected. In V8 platforms like Node and Chrome you can use `Error.captureStackTrace(this, arguments.callee)` in the error's constructor to get the stack trace.
  55. {% highlight javascript %}
  56. function NotFound(message) {
  57. Error.call(this);
  58. Error.captureStackTrace(this, arguments.callee);
  59. this.message = message;
  60. this.statusCode = 404;
  61. }
  62. {% endhighlight %}
  63. When I was researching this article I noticed there's a lot of confusion about inheriting from `Error` and capturing the stack. It's hard to do it properly in every browser. If you want to read more, there's a good Stack Overflow post about it here: [What's a good way to extend Error in JavaScript?](http://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript).
  64. ###Throwing and Catching Errors
  65. You might have noticed I've been quiet about `throw`, and that's because we hardly ever use it anymore. It's more common to see errors passed as the first argument to a callback, or emitted as an `'error'` event's first argument.
  66. If you're using an API like this, you'll probably use something like `if (err) return handleError(err)` at the top of your callback. You can also use `if (err instanceof SpecificError)` to add your own context specific error handling code.
  67. Node developers usually avoid raising exceptions, but if you really think it's necessary you can use `throw new Error('I am Error')` and then `assert.throws` in your tests. I find I hardly ever need to use `throw`.
  68. ###Designing Error Objects
  69. Once you start subclassing `Error` and adding your own properties, you can cause new problems by breaking the [SOLID](http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) principles. To keep your errors clean, ensure an error class only has one responsibility -- don't make Swiss Army knife error objects, or trigger complex behaviours inside their constructors.
  70. You should also create errors in logical places. If you've written a database layer, don't raise the previous `NotFound` error from something that loads data from the database. In this case it would be better to have a `Database.NotFound` error object, or maybe just return `undefined` and then raise `NotFound` at the view layer.
  71. Following the [Liskov substitution principle](http://en.wikipedia.org/wiki/Liskov_substitution_principle) also helps create maintainable error handling code. If you replace the previous `NotFound` error with a new class that has more context-specific information, then the existing code should still work. You'd break this rule if you somehow changed what `notFound.statusCode` did.
  72. ###Conclusion
  73. I create a lot of `Error` classes in my projects, but I rarely use `throw` and `catch`. You should set useful properties in error objects, but use such properties consistently. And, don't cross the streams: HTTP errors have no place in your database code. Or for browser developers, Ajax errors have a place in code that talks to the server, but not code that processes Mustache templates.