140

I am trying to extend Error with ES6 and Babel. It isn't working out.

class MyError extends Error {
  constructor(m) {
    super(m);
  }
}

var error = new Error("ll");
var myerror = new MyError("ll");
console.log(error.message) //shows up correctly
console.log(myerror.message) //shows empty string

The Error object never get the right message set.

Try in Babel REPL.

Now I have seen a few solutions on SO (for example here), but they all seem very un-ES6-y. How to do it in a nice, ES6 way? (That is working in Babel)

1
  • 2
    Following your link to Babel REPL seems to indicate that it works correctly now. I presume it was a bug in Babel that has since been fixed. Commented Dec 15, 2016 at 11:48

14 Answers 14

205

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.

20
  • 1
    @MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . I'd argue that it's better to use this function if it's available, since it provides a more 'native' call stack and prints the name of the error object. Of course, if you're using this solely on the server-side too (Node), then it's also not a problem.
    – Lee Benson
    Commented Sep 30, 2015 at 7:54
  • 4
    @MichaelYounkin I don't think this deserves a downvote. The OP talked about extending errors in ES6. Following that logic, nearly all of ES6 is missing from at least one browser. My solution (with the added func check) provides native coverage in the most widely used browser, fall-back in every other, and 100% coverage in Node.js. I agree that if you what error classname consistently then this.stack = (new Error(message)).stack gets you that... but in practice, this is probably not a huge deal.
    – Lee Benson
    Commented Oct 1, 2015 at 11:34
  • 7
    This does not work in Babel 6: new MyError('foo') instanceof MyError === false
    – Sukima
    Commented Mar 8, 2016 at 3:51
  • 5
    This code pre-compiled with babel as NPM module: extendable-error-class npmjs.com/package/extendable-error-class which is convenient to avoid a dependency on babel-plugin-transform-builtin-extend
    – brillout
    Commented Jul 28, 2016 at 12:34
  • 4
    this.message = message; is redundant with super(message);
    – mathieug
    Commented Jan 20, 2017 at 11:26
40

Combining this answer, this answer and this code, I have made this small "helper" class, that seems to work fine.

class ExtendableError extends Error {
  constructor(message) {
    super();
    this.message = message; 
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}    

// now I can extend

class MyError extends ExtendableError {
  constructor(m) {   
    super(m);
  }
}

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

Try in REPL

8
  • 1
    this.stack = (new Error(message)).stack; -- otherwise the message is missing from the stacktrace
    – Lee Benson
    Commented Sep 23, 2015 at 20:57
  • 3
    I suspect that this doesn't work as needed, because if you do: console.log(myerror instanceof ExtendableError); it still says false.. Commented May 5, 2016 at 21:04
  • 4
    same problem, using instanceof CustomError doesn't work, what's the point extending if you can't use instanceof.
    – gre
    Commented Aug 30, 2016 at 10:16
  • It can be improved adding the message in the Error stack constructor, so it shows the right message at the top of the stack when thrown: this.stack = (new Error(message)).stack;
    – Sebastien
    Commented Sep 28, 2016 at 16:13
  • 1
    myerror.name now returns "Error". Not sure if this is related to later versions of babel.See @sukima's answer below
    – Eric H.
    Commented Feb 25, 2017 at 1:31
27

To finally put this to rest. In Babel 6 it is explicit that the developers do not support extending from built in. Although this trick will not help with things like Map, Set, etc. it does work for Error. This is important as one of the core ideas of a language that can throw an exception is to allow custom Errors. This is doubly important as Promises become more useful since they to are designed to reject an Error.

The sad truth is you still need to perform this the old way in ES2015.

Example in Babel REPL

Custom Error pattern

class MyError {
  constructor(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
}
MyError.prototype = Object.create(Error.prototype);

On the other hand there is a plugin for Babel 6 to allow this.

https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Update: (as of 2016-09-29) After some testing it appears that babel.io does not properly account for all the asserts (extending from a custom extended Error). But in Ember.JS extending Error works as expected: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce

8
  • yep, with the linked babel plugin it works correctly with the accepted answer. (However, the resulting file doesn't work in Node, because it doesn't have Reflect, apparently) Commented Mar 8, 2016 at 10:29
  • 1
    Just as a curiosity, if the ES2016 specs say that builtins are extendable why are vms like v8 and Babel’s es5 transpiling so against it? Is it not a reasonable expectation that a class can extend a class in the same way a prototype chain can come from other prototypes? Why the need for such ceramony hidden in a plugin?
    – Sukima
    Commented Mar 8, 2016 at 11:57
  • This especually frustrating when most use cases just want to make simple objects that share behavior. A custom error that can use Error.toString(). The need to do special hoops and gyrations to accomplish this means most devs will avoid it and resort to bad practices like throwing strings instead of Errors. Or making their own Map like objects. Why the need to deter such OOP methods?
    – Sukima
    Commented Mar 8, 2016 at 12:01
  • In my opinion they are not against it, it's just some technical issue. I am not sure though! You can ask them :) the projects are quite open Commented Mar 8, 2016 at 13:13
  • So far all the responses from simular questions on the topic are left at "babel doesn't support that" I figured that was the end of the conversation. My beef is the lack of support makes a common OOP idiom difficult and I've even had to fight with co-wokers to get them over the boilerplate cruft. I just wish here was a clean alternative workaround. Seems adding a plugin is the best choice then.
    – Sukima
    Commented Mar 8, 2016 at 13:20
17

Edit: Breaking changes in Typescript 2.1

Extending built-ins like Error, Array, and Map may no longer work.

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

Editing Lee Benson original answer a little bit works for me. This also adds stack and additional methods of ExtendableError class to the instance.

class ExtendableError extends Error {
   constructor(message) {
       super(message);
       Object.setPrototypeOf(this, ExtendableError.prototype);
       this.name = this.constructor.name;
   }
   
   dump() {
       return { message: this.message, stack: this.stack }
   }
 }    

class MyError extends ExtendableError {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, MyError.prototype);
    }
}

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

With the latest changes in babel 6, I find transform-builtin-extend no longer working. I ended up using this mixed approach:

export default class MyError {
    constructor (message) {
        this.name = this.constructor.name;
        this.message = message;
        this.stack = (new Error(message)).stack;
    }
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

and

import MyError from './MyError';

export default class MyChildError extends MyError {
    constructor (message) {
        super(message);
    }
}

As a result all these tests pass:

const sut = new MyError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut.name).toBe('MyError');
expect(typeof sut.stack).toBe('string');

const sut = new MyChildError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut).toBeInstanceOf(MyChildError);
expect(sut.name).toBe('MyChildError');
expect(typeof sut.stack).toBe('string');
7

Quoting

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

There is no need for this.stack = (new Error()).stack; trick thanks to super() call.

Although the above codes cannot output the stack trace unless this.stack = (new Error()).stack; or Error.captureStackTrace(this, this.constructor.name); is invoked in Babel. IMO, it maybe one issue in here.

Actually, the stack trace can be output under Chrome console and Node.js v4.2.1 with this code snippets.

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

var myerr = new MyError("test");
console.log(myerr.stack);
console.log(myerr);

Output of Chrome console.

MyError: test
    at MyError (<anonymous>:3:28)
    at <anonymous>:12:19
    at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)
    at Object.InjectedScript.evaluate (<anonymous>:664:21)

Output of Node.js

MyError: test
    at MyError (/home/bsadmin/test/test.js:5:8)
    at Object.<anonymous> (/home/bsadmin/test/test.js:11:13)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:467:10)
    at startup (node.js:134:18)
    at node.js:961:3
4

In addition to @zangw answer, you can define your errors like this:

'use strict';

class UserError extends Error {
  constructor(msg) {
    super(msg);
    this.name = this.constructor.name;
  }
}

// define errors
class MyError extends UserError {}
class MyOtherError extends UserError {}

console.log(new MyError instanceof Error); // true

throw new MyError('My message');

which will throws correct name, message and stacktrace:

MyError: My message
    at UserError (/Users/honzicek/Projects/api/temp.js:5:10)
    at MyError (/Users/honzicek/Projects/api/temp.js:10:1)
    at Object.<anonymous> (/Users/honzicek/Projects/api/temp.js:14:7)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)
    at startup (node.js:117:18)
    at node.js:951:3
2
  • 4
    This does not work: new MyError('foo') instanceof MyError === false.
    – Sukima
    Commented Mar 8, 2016 at 3:50
  • 1
    It does, on Node.js v7.7.3. Commented Mar 24, 2017 at 21:13
3

I prefer more strong syntax than described above. Additional methods at error type will help you to create pretty console.log or something else.

export class CustomError extends Error {
    /**
     * @param {string} message
     * @param {number} [code = 0]
     */
    constructor(message, code = 0) {
        super();

        /**
         * @type {string}
         * @readonly
         */
        this.message = message;

        /**
         * @type {number}
         * @readonly
         */
        this.code = code;

        /**
         * @type {string}
         * @readonly
         */
        this.name = this.constructor.name;

        /**
         * @type {string}
         * @readonly
         */
        this.stack = CustomError.createStack(this);
    }

    /**
     * @return {string}
     */
    toString() {
        return this.getPrettyMessage();
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        return `${this.message} Code: ${this.code}.`;
    }

    /**
     * @param {CustomError} error
     * @return {string}
     * @private
     */
    static createStack(error) {
        return typeof Error.captureStackTrace === 'function'
            ? Error.captureStackTrace(error, error.constructor)
            : (new Error()).stack;
    }
}

To test this code you can run something similar:

try {
    throw new CustomError('Custom error was thrown!');
} catch (e) {
    const message = e.getPrettyMessage();

    console.warn(message);
}

Extending of CustomError type are welcome. It is possible to add some specific functionality to the extended type or override existing. For example.

export class RequestError extends CustomError {
    /**
     * @param {string} message
     * @param {string} requestUrl
     * @param {number} [code = 0]
     */
    constructor(message, requestUrl, code = 0) {
        super(message, code);

        /**
         * @type {string}
         * @readonly
         */
        this.requestUrl = requestUrl;
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        const base = super.getPrettyMessage();

        return `${base} Request URL: ${this.requestUrl}.`;
    }
}
2

I am trying to extend Error with ES6

That class MyError extends Error {…} syntax is correct.

Notice that transpilers still do have problems with inheriting from builtin objects. In your case,

var err = super(m);
Object.assign(this, err);

seems to fix the problem.

5
  • True! But the message is not set anyway - I will write a new example. Commented Jun 27, 2015 at 14:36
  • I have rewritten the example now Commented Jun 27, 2015 at 14:37
  • Doesn't work for me Commented Jun 27, 2015 at 14:43
  • The "super(m)" will return an empty object, apparently. So Object.assign doesn't help. Commented Jun 27, 2015 at 15:04
  • @KarelBílek: What browser are you using? Error.call() does return a new error instance for me.
    – Bergi
    Commented Jun 27, 2015 at 15:59
2

Given this the accepted answer no longer works you could always use a factory as an alternative (repl):

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

// now I can extend
const MyError = ErrorFactory("MyError");


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

1
  • The accepted answer still works for me, if you have the necessary babel plugins. Thanks for this answer too, though! Commented Oct 10, 2016 at 11:46
1

As @sukima mentions, you cannot extend native JS. The OP's question cannot be answered.

Similar to Melbourne2991's answer, I did used a factory rather, but followed MDN's recommendation for customer error types.

function extendError(className){
  function CustomError(message){
    this.name = className;
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
  CustomError.prototype = Object.create(Error.prototype);
  CustomError.prototype.constructor = CustomError;
  return CustomError;
}
1

Not using Babel, but in plain ES6, the following seems to work fine for me:

class CustomError extends Error {
    constructor(...args) {
        super(...args);
        this.name = this.constructor.name;
    }
}

Testing from REPL:

> const ce = new CustomError('foobar');
> ce.name
'CustomError'
> ce.message
'foobar'
> ce instanceof CustomError
true
> ce.stack
'CustomError: foobar\n    at CustomError (repl:3:1)\n ...'

As you can see, the stack contains both the error name and message. I'm not sure if I'm missing something, but all the other answers seem to over-complicate things.

0
1

This works for me:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}
0

I improved a little the solution of @Lee Benson this way:

extendableError.js

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


}

export default ExtendableError

an example of an error

import ExtendableError from './ExtendableError'

const AuthorizationErrors = {
    NOT_AUTHORIZED: 401,
    BAD_PROFILE_TYPE: 402,
    ROLE_NOT_ATTRIBUTED: 403
}

class AuthorizationError extends ExtendableError {
    static errors = AuthorizationErrors 
}

export default AuthorizationError 

Then you are able to group errors while having option specifiers to decide what to do differently in some of your application specific situations

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )

Not the answer you're looking for? Browse other questions tagged or ask your own question.