Extending Error in Javascript with ES6 syntax & Babel

 ̄綄美尐妖づ 提交于 2019-11-26 21:17:10

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.

Karel Bílek

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

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

Artur Aleksanyan

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

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

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

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

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.

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);
Eric H.

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

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

This works for me:

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!