What's a Good Way to Extend Error in JavaScript

Extending Error in Javascript with ES6 syntax & Babel

Based on Karel Bílek's answer, I'd make a small change to the constructor:

class ExtendableError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error(message)).stack;
}
}
}

// now I can extend

class MyError extends ExtendableError {}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

This will print MyError in the stack, and not the generic Error.

It will also add the error message to the stack trace - which was missing from Karel's example.

It will also use captureStackTrace if it's available.

With Babel 6, you need transform-builtin-extend (npm) for this to work.

How to extend many Error classes dynamically?

Easy, use an anonymous class expression and assign it to a global variable:

function declareError(name) {  window[name] = class extends Error {    constructor (message) {      super(message)      this.name = name;    }  }}function declareErrors(errors) {  errors.forEach(declareError);}declareErrors(['NotFoundError', 'TimeoutError', 'AclError']);
const error = new AclError("Bad Darius!");console.log( error.name, error.message, error instanceof Error, error instanceof AclError);

How do I create a custom Error in JavaScript?

Update your code to assign your prototype to the Error.prototype and the instanceof and your asserts work.

function NotImplementedError(message = "") {
this.name = "NotImplementedError";
this.message = message;
}
NotImplementedError.prototype = Error.prototype;

However, I would just throw your own object and just check the name property.

throw {name : "NotImplementedError", message : "too lazy to implement"}; 

Edit based on comments

After looking at the comments and trying to remember why I would assign prototype to Error.prototype instead of new Error() like Nicholas Zakas did in his article, I created a jsFiddle with the code below:

function NotImplementedError(message = "") {
this.name = "NotImplementedError";
this.message = message;
}
NotImplementedError.prototype = Error.prototype;

function NotImplementedError2(message = "") {
this.message = message;
}
NotImplementedError2.prototype = new Error();

try {
var e = new NotImplementedError("NotImplementedError message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError = " + (ex1 instanceof NotImplementedError));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}

try {
var e = new NotImplementedError2("NotImplementedError2 message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError2 = " + (ex1 instanceof NotImplementedError2));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}

When and why is it good to create custom exceptions?

The Object.assign approach is less robust and more of a hack, it is better to create custom error class. There is already an in-depth discussion on SO.

As you want to use additional fields, at most, introduce 2-3 custom classes for internal errors, but even that is often overkill:

  • one for NetworkError with location, path and status
  • one for UiError with component and problematic data state and maybe a message code for i18n
  • and one generic RuntimeError, or similar, for unknown cases

It makes little sense to have an error class per each potential occurrence. Unlike Java, there are no checked exceptions in JavaScript, and the goal is to have just enough data to troubleshoot the problem, without over engineering it. If you can meaningfully capture and then display in a dialog more data than message string would hold, go for it.

When designing custom errors, start with where and how you will process and show this information. Then see if you can easily collect this data where you throw it. If you do not have global error dialog or centralized error reporting, maybe just the default Error is enough, and you can place all the data into the message.

There is one special case, when you want to use errors as means of controlling the logic. Try to avoid it as much as possible, JavaScript is very flexible to not use throw as a way to let upper layer choose a different execution path. However, it is sometimes used to re-try network requests, and then it should have enough data for that.

Built-in Error object already has following fields:

  • name
  • message
  • stack

In each error, stack and message are two crucial pieces of information helping to fix the problem. Therefore it is important, when you re-throw it, to use something like this (for everything non-IE):

catch (err) {
throw new Error('New error message with added info', { cause: err });
}

Last, it helps to check what others are doing:

  • GraphQL's GraphQLError
  • Error handling hooks in VueJS (it has no custom errors)

And, JavaScript has not just Error, but also:

  • EvalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError
  • AggregateError

You can also throw them when appropriate.

Note, that most UI frameworks handling views do not have custom error classes, nor do they need one.

Extending JavaScript errors / exceptions

What you're doing is fine. It's Error that's the problem.

On this line:

Error.prototype.constructor.apply(this, arguments);

...you're calling Error (indirectly) as a normal function call rather than as part of a new expression, and the defined behavior for Error when you call it as a non-constructor function is to create a new, blank Error and return it (rather than populating this).

Now, in the normal case, what you're doing with the prototype would make the standard check for whether it was called as a constructor (if (this instanceof Error)) work. Unfortunately, it doesn't seem to be doing that, and whatever check it uses to determine how it was called doesn't seem to be immediately amenable to what you're trying to do. Even this (which is just a test, not meant to be something you'd actually do):

MyError.prototype = Error.prototype; // You wouldn't normally do this, it's just a test

...didn't solve it.

This answer to another Stack Overflow question points to a possible workaround (basically, let Error create the object and then return that from MyError):

function MyError(message) {
var e = new Error(message);
// ...apply your enhancements to `e`
return e;
}

Not amazingly satisfying as you'd have to put your extensions directly on the object rather than use the prototype chain, but if Error refusees to play ball, your options are a bit limited...

Typescript - Extending Error class

Are you using typescript version 2.1, and transpiling to ES5? Check this section of the breaking changes page for possible issues and workaround: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work

The relevant bit:

As a recommendation, you can manually adjust the prototype immediately after any super(...) calls.

class FooError extends Error {
constructor(m: string) {
super(m);

// Set the prototype explicitly.
Object.setPrototypeOf(this, FooError.prototype);
}

sayHello() {
return "hello " + this.message;
}
}

However, any subclass of FooError will have to manually set the prototype as well. For runtimes that don't support Object.setPrototypeOf, you may instead be able to use __proto__.

Unfortunately, these workarounds will not work on Internet Explorer 10 and prior. One can manually copy methods from the prototype onto the instance itself (i.e. FooError.prototype onto this), but the prototype chain itself cannot be fixed.

Extending Error with ES6 Class

Found out you can't extend Error.

There's a package es6-error with an Error that you can extend.

import ExtendableError from 'es6-error';

class MyError extends ExtendableError {
constructor(message) {
super()
this.name = 'MyError'
this.message = message
this.stack = (new Error(message)).stack
return this
}
doSomething () {
return this.message + " dolphin"
}
}

let myError = new MyError('Invalid Form.')

console.log(myError.doSomething())


Related Topics



Leave a reply



Submit