Custom JavaScript Errors in ES6

A few years ago I gave a talk at MountainWest JS called Error Handling in node.js. In that talk I extolled the virtues of creating custom error objects by extending the built-in Error type. Most of the content was inspired by Guillermo’s seminal A String is Not an Error. Let’s revisit that advice in the world of ES6 classes.

Why Do We Care About Error Objects?

Error objects are the only way to get stack traces in JavaScript. If you callback with or throw or reject a promise with a string instead of an Error, you won’t be able to trace where the error came from.

Here’s a simple example:

throw "this is not an error"

When you run this file in Node 6 you’ll see:

test.js:1
(function (exports, require, module, __filename, __dirname) { throw "this is not an error"
^
this is not an error

Here’s what creating a real Error object looks like

throw new Error("this is not an error")

The output which is much better:

test.js:1
(function (exports, require, module, __filename, __dirname) { throw new Error("this is not an error")
^

Error: this is not an error
at Object.<anonymous> (test.js:1:69)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Function.Module.runMain (module.js:441:10)
at startup (node.js

Tip: There’s an ESLint rule no-throw-literal that will catch many of these errors.

Why Bother with Custom Errors?

Given that Error objects are much better than strings, what if we need to include more than a message with our error? For example what if we also want to include an error code?

One of the common ways to handle this is to append errors with custom properties (which is used heavily in node.js itself.)

var error = new Error("Could not access the file")
error.code = "EACCES"

This is a very reliable approach, but has two downsides:

  1. It can be a lot of lines of code (1 line per extra property)
  2. Your error instance can’t have any custom methods

Why would you want these extra features?

Think about the case of dealing with errors from a server request. Maybe multiple form fields had errors in them or maybe you need a CAPTCHA included to continue. Encapsulating all of that logic in a custom error class might make a lot of sense:

The idea here is that we can create a special ServerError class which can take all of the data from the bad server response and append the error object with critical information including statusCode, as well as any error code, or maybe even an array of with all of the forms fields which are failing. We could even give our ServerErrror special methods like getLocalizedError() which wouldn’t be available with a standard error object.

How Do We Make Custom Errors?

Making custom error objects used to be a little bit tricky! Lots of blog posts had to be written about it (mostly because inheritance in JS is a little bit tricky.) Thankfully ES6 has made this a little bit easier.

Here’s an example of creating a custom error with ES6:

class MyError extends Error {}

That was pretty simple! Let’s see what sort of stack trace this error creates:

Here’s what gets logged:

Error: there was a problem
at GoodError (custom-errors.js:1:63)
at getError (custom-errors.js:4:25)
at Object.<anonymous (custom-errors.js:7:1)

This looks like a normal stack trace except for one thing. It includes the error constructor in the stack trace. This is not actually a big deal, but if you want to kick that you can do this:

That curious Error.captureStackTrace() method which exists in node, but not necessarily in your web browser, updates the stack trace in a way that excludes the constructor. With those changes here is the stack trace:

Error: there was a problem
at getError (fancy-errors.js:9:5)
at Object.<anonymous> (fancy-errors.js:12:1)

Tl;dr

  1. Always use error objects instead of strings to preserve a stack trace
  2. Appending common properties to errors is used by node.js to provide additional context
  3. Custom error objects allow for adding your own methods and make capturing additional metadata into your errors a lot of easier
  4. class MyError extends Error {} is basically good enough
  5. Override the constructor and use Error.captureStackTrace if you want to streamline the stack trace a bit.
Show your support

Clapping shows how much you appreciated Jamund Ferguson’s story.