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)

Indolering
  • 3,058
  • 30
  • 45
Karel Bílek
  • 36,467
  • 31
  • 94
  • 149
  • 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. – kybernetikos Dec 15 '16 at 11:48

14 Answers14

204

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.

Lee Benson
  • 11,185
  • 6
  • 43
  • 57
  • You are right, I have accepted this instead of my own answer :) – Karel Bílek Sep 23 '15 at 23:52
  • Error.captureStackTrace doesn't work in Firefox. Karel Bilek's answer is better. – Michael Younkin Sep 30 '15 at 03:35
  • 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 Sep 30 '15 at 07:54
  • Good tip, though I think it's still a judgment call. Some people might prefer to produce a similar stack trace in every browser for debugging purposes. – Michael Younkin Sep 30 '15 at 20:48
  • 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 Oct 01 '15 at 11:34
  • 1
    Your argument about ES6 doesn't make sense - OP specifically requested something that works in Babel, which compiles ES6 so it can run in most browsers. In any case, I downvoted your answer because it's misleading, and someone happening upon this question could be confused. – Michael Younkin Oct 16 '15 at 07:34
  • Anyone that copies and pastes an answer from SO without reading context, comments and figuring out whether it's relevant to their use-case, probably isn't using SO correctly anyway. In any case, checking function availability and falling back to a lesser version (which `this.stack = (new Error(message)).stack` is, because it messes up the stack trace - at least in Firefox) is probably good advice for any API, especially with something like the `Error` class, which is fraught with vendor-specific extensions. Why not use `Error.captureStackTrace` where it's available? – Lee Benson Oct 19 '15 at 23:39
  • Missing semicolon in `Error.capturaStackTrace` :) – Joaquín M Feb 03 '16 at 19:07
  • @JoaquínM - semi-colons _are_ optional in JS, but nicely spotted :-p – Lee Benson Feb 03 '16 at 19:46
  • @LeeBenson will you be OK if I edit your answer and add your own `if (typeof Error.....` code to it? – Karel Bílek Mar 03 '16 at 21:38
  • 1
    Hm, the answer actually doesn't work for me in babel6 :( – Karel Bílek Mar 04 '16 at 02:10
  • 7
    This does not work in Babel 6: `new MyError('foo') instanceof MyError === false` – Sukima Mar 08 '16 at 03:51
  • It would appear that both the JavaScript Specification committee and the Babel developers are hard set to stop developers from making their own custom Errors without a painful amount of hard to remember boilerplate. As far as I can tell the consensus in the community is to ignore the `instanceof` error checks and ignore promise rejections using custom errors. – Sukima Mar 08 '16 at 03:54
  • @Sukima the code above, with the module listed, doesn't work for you? :( – Karel Bílek Mar 08 '16 at 10:17
  • 5
    This code pre-compiled with babel as NPM module: `extendable-error-class` https://www.npmjs.com/package/extendable-error-class which is convenient to avoid a dependency on babel-plugin-transform-builtin-extend – brillout Jul 28 '16 at 12:34
  • If `Error.captureStackTrace` doesn't exist, it's unnecessary to execute `this.stack = (new Error(message)).stack;` After call to super constructor the stack property already exists. – smirnov Sep 20 '16 at 14:12
  • For node.js users, the [es6-error](https://github.com/bjyoungblood/es6-error) and the [babel-plugin-transform-builtin-extend](https://github.com/bjyoungblood/es6-error) are just what you need. – herve Nov 11 '16 at 14:02
  • 1
    you don't need `constructor(m) { super(m);}` in derived class, it's default behavior. – shabunc Dec 05 '16 at 20:02
  • @shabunc, you're right - I've removed it. This was mostly a stub to show how you could 'extend' the sub-class. In production, I have several error classes that do auto-logging when instantiated, so I generally build on the default constructor. – Lee Benson Dec 05 '16 at 20:30
  • 4
    `this.message = message;` is redundant with `super(message);` – mathieug Jan 20 '17 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

Cœur
  • 37,241
  • 25
  • 195
  • 267
Karel Bílek
  • 36,467
  • 31
  • 94
  • 149
  • 1
    `this.stack = (new Error(message)).stack;` -- otherwise the message is missing from the stacktrace – Lee Benson Sep 23 '15 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.. – Mauno Vähä May 05 '16 at 21:04
  • 4
    same problem, using instanceof CustomError doesn't work, what's the point extending if you can't use instanceof. – gre Aug 30 '16 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 Sep 28 '16 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. Feb 25 '17 at 01:31
  • @gre I think it doesn't work if you're using a translator like Babel, but might work with native ES6. – z0r Sep 26 '17 at 03:04
  • @z0r oh I see, interesting! – gre Sep 27 '17 at 09:14
  • why do you need two `extends`ed classes? All the second one is doing seems to be calling the constructor of the first, no? (I'm sure I'm missing something) – 1252748 Apr 18 '18 at 02:06
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

Sukima
  • 9,965
  • 3
  • 46
  • 60
  • 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) – Karel Bílek Mar 08 '16 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 Mar 08 '16 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 Mar 08 '16 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 – Karel Bílek Mar 08 '16 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 Mar 08 '16 at 13:20
  • IIRC I think the technical limitation is that v8 and some other JS engines incorrectly implemented builtin objects and prevent them from being extended. Babel is only attempting to remain compatible with the current JS engines. – Sukima Mar 08 '16 at 13:22
  • @Sukima what about this? https://github.com/bluejamesbond/TraceError.js/blob/master/src/Exception.js I have Extended the error in a peer file called TraceError.js – Mathew Kurian Mar 19 '16 at 23:22
  • This is a good solution for large-ish projects. It is concise and easy to read. Kudos. I wish you didn’t need so much ceramony for a simple JS file/module though. – Sukima Mar 19 '16 at 23:26
15

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);
Community
  • 1
  • 1
Artur Aleksanyan
  • 480
  • 5
  • 10
  • 1
    You need to call `Object.setPrototypeOf` in the `MyError` constructor also. https://stackoverflow.com/a/41102306/186334 https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work – CallMeLaNN Jul 02 '18 at 17:00
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');
Diego Ferri
  • 2,657
  • 2
  • 27
  • 35
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
zangw
  • 43,869
  • 19
  • 177
  • 214
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
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}.`;
    }
}
B. Bohdan
  • 480
  • 4
  • 12
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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • True! But the message is not set anyway - I will write a new example. – Karel Bílek Jun 27 '15 at 14:36
  • I have rewritten the example now – Karel Bílek Jun 27 '15 at 14:37
  • [Doesn't work for me](https://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code=class%20MyError%20extends%20Error%20%7B%0A%20%20constructor(m)%20%7B%0A%20%20%20%20var%20err%20%3D%20super(m)%3B%0A%20%20%20%20Object.assign(this%2C%20err)%3B%0A%20%20%7D%0A%7D%0A%0Avar%20myerror%20%3D%20new%20MyError(%22llg%22)%3B%0Aconsole.log(myerror.message)%20%2F%2Fshows%20empty%20string) – Karel Bílek Jun 27 '15 at 14:43
  • The "super(m)" will return an empty object, apparently. So Object.assign doesn't help. – Karel Bílek Jun 27 '15 at 15:04
  • @KarelBílek: What browser are you using? `Error.call()` does return a new error instance for me. – Bergi Jun 27 '15 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);
Melbourne2991
  • 11,707
  • 12
  • 44
  • 82
  • The accepted answer still works for me, if you have the necessary babel plugins. Thanks for this answer too, though! – Karel Bílek Oct 10 '16 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;
}
Community
  • 1
  • 1
Eric H.
  • 6,894
  • 8
  • 43
  • 62
1

This works for me:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}
Michael Liquori
  • 619
  • 10
  • 16
0

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.

JHH
  • 8,567
  • 8
  • 47
  • 91
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 )
Zied Hamdi
  • 2,400
  • 1
  • 25
  • 40