Subclassing `Error` in modern JavaScript

My go-to snippet for cleaner custom errors

tl;dr: Don’t forget the name property. In ES2015 and above,

class MyError extends Error {}
Object.defineProperty(MyError.prototype, 'name', {
  value: 'MyError', // can even just reference `MyError.name`
});

…gets you a custom error class of MyError with a non-enumerable, immutable name value properly set on the prototype. There are plenty of was of creating custom errors, but this gets you close to what JavaScript does.

In Nuclide we pretty frequently subclass JavaScript’s built-in Error object to throw custom errors indicating something exceptional in our domain logic.

Subclassing Error may not be the first thing you want to reach for — you can get pretty good coverage in cases of unexpected types with the built-in RangeError and TypeError, or you can do what Node does and stick a code property on every error you create. You can even get a great developer experience by setting name on an error object after you’ve created it.

That said, there are plenty of advantages having an entire class of objects with predictable shapes dedicated to what you’re trying to express, especially if you’re using a typechecker like Flow or TypeScript.

One thing I see a lot is the omission of the name property on custom Errors entirely. When subclassing Error, this will disambiguate your type from the superclass during property checks, in toString(), and in tools, improving developer experience and reducing stress down the road when you’re reading through logs.

To account for this, you can set the name property on every instance of the custom error using property initializers:

class MyError extends Error {
  name = 'MyError';
}

For those not familiar, it desugars to this:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}

This looks clean, works, and achieves a nice toString() value of MyError. Then again, this isn’t exactly what JavaScript does for its built-in Errors, but it should be fine for most cases.

In JavaScript’s own errors, ‘name’ is a non-enumerable property on the Error prototype (and its friends)

It’s worth checking out MDN’s docs on Error, which clarify that on the built-in Error, name lives on the prototype, not on each error object. Additionally, it’s not enumerable, meaning that it won’t be enumerated in a for in, JSON.stringify, or appear in a call to Object.keys:

Object.keys(Error.prototype); // []

In fact, this is true of the error objects themselves; even the message property isn’t enumerable!

const err = new Error('oh noes!');

err.hasOwnProperty('message'); // true
Object.keys(err); // []

The name on the prototype is apparently both writable and configurable:

const err = new Error('oh noes!');

// don't do this at home! ;)
Error.prototype.name = 'lol';
err.toString(); // 'lol: oh noes!'

delete Error.prototype.name; // true

It’s up to you if you want to go to the lengths of recreating this in your own errors, but getting this right may save you should your users enumerate an error object like they would one of JavaScript’s. Let’s do it:

class MyError extends Error {}
Object.defineProperty(MyError.prototype, 'name', {
  enumerable: false, // this is the default
  configurable: true,
  value: 'MyError',
  writable: true,
});

This best approximates JavaScript’s built-in Error types.

Personally, I like the idea that someone can’t mess around with the presence of value of my error’s name property, so in my custom error I leave off the last two properties in that descriptor, only setting the value:

class MyError extends Error {}
Object.defineProperty(MyError.prototype, 'name', {
  value: 'MyError',
});

Either way, if someone wanted their own name in addition to your custom error, they could always just set the property immediately after construction and that works fine:

const myOtherError = new MyError();
myOtherError.name = 'MyOtherError';

myOtherError.toString(); // 'MyOtherError'

Note, assigning to name here assigned to the object’s own name property, not its prototype’s. Even if its prototype’s name property were non-writable or non-configurable, this would still be fine.

So there you have it — probably more than you’d ever like to know about JavaScript errors. Hopefully this means less time spent scouring the internet and playing in the repl :)