if (error instanceof ProgrammerError) {
    // TODO: fix code
} else {
    // Handle expected failure cases
}

This one flew under the radar for me for quite a while. Every once in a while I wondered when I would get a chance to play with some of the new Symbol.species magic that let’s us hook into background behaviours.

Geoff, 2015 isn’t new, it’s almost 2018 now…

As the hapi team was ramping up for launch of hapi@17, a complete async / await-based rewrite of the framework, the question arose of how to distinguish between fatal programmer errors and system errors. Treating a failed assertion the same way as a failed http request would rob framework users of the ability to build robust error handling logic in the new async / await world.

How can we tell if an Error is a programmer error or a system error?

Enter Symbol.hasInstance. This ‘well-known symbol’ is defined as part of the ES2015 spec and allows custom implementations of the logic to resolve an instanceof check.

Let’s say that we use Boom to represent http client errors in our application. We also have a body of code that uses another type of error, such as VError, to represent errors. Boom acts on plain Error instances and adds the .isBoom property, among other things. On the other hand, VError errors will all inherit from a common base class.

Mixing instanceof and custom property checks is ugly and inconsistent. Can we do better? The answer, of course, is yes!

class Boom {
  static [Symbol.hasInstance](error) {
    return error?.isBoom === true;
  }
}

We’ve added a static method to the Boom class whose name is the well-known Symbol.hasInstance symbol. This method will be invoked by the javascript runtime anytime it sees something instanceof Boom.

Even though error in the example is not actually an instance of the Boom class (it is just a plain Error), we’ve enabled it to be treated as a Boom error from the perspective of an instanceof check.

And there we are! I hope you enjoyed this little peak behind the curtains of what will make a future version of Boom special.

But how does this help me distinguish programmer errors?

Oh right, we got a bit side-tracked there, didn’t we. Let’s say we take our learnings from above and implement a custom ProgrammerError class that we can use to distinguish errors in implementation from errors in execution. It might look something like this:

class ProgrammerError {
  static [Symbol.hasInstance](error) {
    return error instanceof Error
      && !(error instanceof Boom)
      && !(error instanceof VError);
  }
}

And there you have it (again)! We can cleanly distinguish between unusual, but expected errors from those that need to be fixed before even shipping.


Originally published on Medium.