Extending Error in Javascript with ES6 syntax & Babel

JavascriptEcmascript 6BabeljsTranspiler

Javascript Problem Overview


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)

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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

Solution 3 - Javascript

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

Solution 4 - Javascript

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);

Solution 5 - Javascript

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');

Solution 6 - Javascript

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

Solution 7 - Javascript

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

Solution 8 - Javascript

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}.`;
    }
}

Solution 9 - Javascript

> 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.

Solution 10 - Javascript

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);

Solution 11 - Javascript

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;
}

Solution 12 - Javascript

This works for me:

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

Solution 13 - Javascript

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.

Solution 14 - Javascript

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 )

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKarel B&#237;lekView Question on Stackoverflow
Solution 1 - JavascriptLee BensonView Answer on Stackoverflow
Solution 2 - JavascriptKarel BílekView Answer on Stackoverflow
Solution 3 - JavascriptSukimaView Answer on Stackoverflow
Solution 4 - JavascriptArtur AleksanyanView Answer on Stackoverflow
Solution 5 - JavascriptDiego FerriView Answer on Stackoverflow
Solution 6 - JavascriptzangwView Answer on Stackoverflow
Solution 7 - JavascriptHonza StepanovskyView Answer on Stackoverflow
Solution 8 - JavascriptB. BohdanView Answer on Stackoverflow
Solution 9 - JavascriptBergiView Answer on Stackoverflow
Solution 10 - JavascriptMelbourne2991View Answer on Stackoverflow
Solution 11 - JavascriptEric H.View Answer on Stackoverflow
Solution 12 - JavascriptMichael LiquoriView Answer on Stackoverflow
Solution 13 - JavascriptJHHView Answer on Stackoverflow
Solution 14 - JavascriptZied HamdiView Answer on Stackoverflow