Almost all applications eventually need to create custom error classes. Often, these custom errors are "wrappers" for existing ones, that add application-specific context. For example, a node database migration tool might try-catch each migration, and re-raise a MigrationError
which tells the user migration 2020-12-29-create-user-table.sql failed
, whereas the underlying error might say something like Syntax error near ';'
, which is what comes from the database client.
The idea here is a WrappedError
class which extends Error
to standardise the Right Way to do this. It would need to be able to provide, at minimum, a custom message and a cause
property, which is the error (or other throwable) object which it's wrapping. The best existing prior art I've come across is verror
: GitHub - TritonDataCenter/node-verror: Rich JavaScript errors . The design is well thought through, simple and useful (although the naming is a bit cryptic).
To borrow some examples from verror:
var err1 = new Error('No such file or directory');
var err2 = new VError(err1, 'failed to stat "%s"', '/junk');
var err3 = new VError(err2, 'request failed');
console.error(err3.message)
This prints:
request failed: failed to stat "/junk": No such file or directory
The idea is that each layer in the stack annotates the error with a description of what it was doing. The end result is a message that explains what happened at each level.
The two main goals of verror are stated as:
- Make it easy to construct clear, complete error messages intended for people. Clear error messages greatly improve both user experience and debuggability, so we wanted to make it easy to build them. That's why the constructor takes printf-style arguments.
- Make it easy to construct objects with programmatically-accessible metadata (which we call informational properties). Instead of just saying "connection refused while connecting to 192.168.1.2:80", you can add properties like
"ip": "192.168.1.2"
and"tcpPort": 80
. This can be used for feeding into monitoring systems, analyzing large numbers of Errors (as from a log file), or localizing error messages.
There are some problems with this functionality existing in a library:
- its maintenance status is unclear. Verror hasn't seen any GitHub changes for over a year, and the npm package hasn't been published for four years.
- it being an individual (and possibly unmaintained) library means that tooling is unlikely to take account of it. It doesn't make sense for error reporting systems like Sentry to support verror out of the box. If it was part of the language spec it absolutely would. There could be first-class UI for an on-call engineer to dive into production errors, seeing the top-level one first and click through the chain of causes.
- some libraries do use verror, but most don't, using instead a custom error-wrapping system which doesn't always maintain the original error, or capture it in a different way. Meaning investigating root-cause issues can become difficult or impossible.
- Standard reason of it being nice to be able to avoid a dependency for very important, very broadly useful functionality.
Being part of the language spec would cause a positive feedback loop of goodness. Tooling could start adding UI/DX around wrapped errors, which would encourage library and application developers to wrap their errors in a standardised way, further improving tooling and the ecosystem in general.
Just having an accessible chain of causes would be valuable, but I think it'd be worth incorporating the rest of the verror features too - a standard way of defining additional properties on an error, and printf style formatting for error messages, which would make them easier to group and anonymise. All of this being in the language spec would allow debugging tools to greatly improve, and would become available in a standard way to all js environments (node, browsers, deno, plv8, etc. etc.). It'd be a good opportunity to add those features, but if printf for example was controversial it could be changed or removed.