In this blog post, we examine the ECMAScript proposal “Error cause” (by Chengzhong Wu and Hemanth HM). It describes a feature where instances of Error
can optionally specify that they were caused by another error.
Sometimes, we catch errors that are thrown during a more deeply nested function call and would like to attach more information to it:
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
const text = readText(filePath);
const json = JSON.parse(text);
return processJson(json);
} catch (error) {
// (A)
}
});
}
The statements inside the try
clause may throw all kinds of errors. In most cases, an error won’t be aware of the path of the file that caused it. That‘s why we would like to attach that information in line A.
The proposal enables us to do the following:
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
// ···
} catch (error) {
throw new Error(`While processing ${filePath}`, {cause: error});
}
});
}
Error
and its subclasses now have an object with options as a second parameter. The first supported option is .cause
– the error that caused the current error.
If you subclass Error
, it makes sense to support the second parameter with options:
class MyCustomError extends Error {
constructor(message, options) {
super(message, options);
// ···
}
}
.cause
AggregateError
(created by Promise.any()
) If Promise.any()
rejects its returned Promise, the rejection value is an instance of AggregateError
that records which (zero or more) errors caused the rejection:
class AggregateError {
constructor(errors: Iterable<any>, message: string);
get errors(): Array<any>;
get message(): string;
}
AggregateError
is a reasonable workaround if .cause
is not supported on an engine that you are targeting, however:
AggregateError
works best if we are handling multiple concurrent invocations.Error
with .cause
works best for single non-concurrent calls.The following custom error class supports chaining.
/**
* This subclass of Error supports chaining.
* If available, it uses the built-in support for property `.cause`.
* Otherwise, it sets it up itself.
*
* @see https://github.com/tc39/proposal-error-cause
*/
class CausedError extends Error {
constructor(message, options) {
super(message, options);
if ((isObject(options) && 'cause' in options) && !('cause' in this)) {
const cause = options.cause;
this.cause = cause;
if ('stack' in cause) {
this.stack = this.stack + '\nCAUSE: ' + cause.stack;
}
}
}
}
function isObject(value) {
return value !== null && typeof value === 'object';
}
There are several libraries that support chaining errors. Three examples: