使用ES6语法和Babel扩展Javascript中的错误

140

我正在尝试使用ES6和Babel扩展Error,但似乎无法实现。

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

Error对象从未获得正确的消息设置。

在 Babel REPL 中尝试

现在我已经看到 SO 上的一些解决方案 (例如这里),但它们似乎都不是很符合 ES6 的风格。如何以一种美观、ES6 的方式实现呢?(能在 Babel 中工作的那种)


2
按照您提供的Babel REPL链接,似乎现在它可以正常工作了。我推测这是Babel中的一个错误,已经得到修复。 - kybernetikos
14个回答

204

根据Karel Bílek的回答,我会对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);

这将会在堆栈中打印MyError,而非通用的Error

它还将错误信息添加到堆栈跟踪中——这是 Karel 的示例中缺少的。

如果可用,它还将使用captureStackTrace

使用 Babel 6,你需要 transform-builtin-extendnpm)才能正常工作。


1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . 我认为如果这个函数可用的话最好使用它,因为它提供了更“本地”的调用堆栈并打印错误对象的名称。当然,如果你只在服务器端使用它(Node),那也不是问题。 - Lee Benson
4
我认为这并不值得被踩。 OP谈到了在ES6中扩展错误的问题。按照这个逻辑,几乎所有ES6至少在一个浏览器中都无法使用。我的解决方案(加上添加的func check)在最常用的浏览器中提供本机覆盖,其他浏览器提供回退,并在Node.js中实现100%的覆盖范围。我同意,如果您想要一致的错误类名,则this.stack =(new Error(message)).stack可以帮助您实现这一点……但实际上,这可能并不是非常重要的事情。 - Lee Benson
7
在Babel 6中,这个不起作用:new MyError('foo') instanceof MyError === false - Sukima
5
这段代码已经通过 babel 预编译为 NPM 模块:extendable-error-class https://www.npmjs.com/package/extendable-error-class,使用它可以方便地避免对 babel-plugin-transform-builtin-extend 的依赖。 - brillout
4
this.message = message; 这句话与 super(message); 重复了。请删除 this.message = message; - mathieug
显示剩余15条评论

40

结合这个答案这个答案这段代码,我创建了这个小的“helper”类,看起来工作正常。

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

在 REPL 中尝试


1
this.stack = (new Error(message)).stack; -- 否则堆栈跟踪中将缺少消息 - Lee Benson
3
我怀疑这并没有按需工作,因为如果您执行以下操作:console.log(myerror instanceof ExtendableError);它仍然会返回false... - Mauno Vähä
4
相同的问题,使用 instanceof CustomError 无法解决,如果无法使用 instanceof,扩展的意义在哪里。 - gre
1
myerror.name 现在返回 "Error"。不确定是否与后来的babel版本有关。请参阅@sukima的答案。 - Eric H.
@z0r 哦,我明白了,很有趣! - gre
显示剩余3条评论

27
为了最终解决这个问题。在Babel 6中,开发者明确表示不支持从内置对象进行扩展。虽然这个技巧对于像MapSet等这样的东西没有帮助,但是它对于Error是有效的。这一点非常重要,因为抛出异常的语言的核心思想之一是允许自定义错误。这一点变得更加重要,因为Promises变得越来越有用,因为它们也被设计为拒绝错误
不幸的是,在ES2015中仍然需要以旧方式执行此操作。

Babel REPL中的示例

自定义错误模式

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

另一方面,Babel 6有一个插件可以实现这个功能。

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

更新:(截至2016年9月29日)经过一些测试,发现babel.io没有正确考虑所有的断言(从自定义扩展错误中扩展)。但在Ember.JS中,扩展错误的效果如预期:https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


是的,使用链接的Babel插件,它可以与接受的答案正确地工作。(然而,生成的文件在Node中不起作用,因为显然它没有Reflect) - Karel Bílek
1
只是出于好奇,如果ES2016规范表明内置函数是可扩展的,为什么像v8和Babel的es5转译器会如此反对它呢?一个类可以像原型链可以来自其他原型一样扩展另一个类,这不是一个合理的期望吗?为什么需要在插件中隐藏这样的仪式感? - Sukima
在我看来,他们并不反对,只是一些技术问题。虽然我不确定!你可以问问他们 :) 这些项目非常开放。 - Karel Bílek
我记得技术限制在于v8和其他一些JS引擎错误地实现了内置对象并阻止它们被扩展。Babel只是试图保持与当前JS引擎的兼容性。 - Sukima
这是一个适用于较大项目的好解决方案。它简洁易读,值得称赞。但我希望在处理简单的JS文件/模块时不需要太多的仪式感。 - Sukima
显示剩余3条评论

16

编辑: Typescript 2.1 中的重大更改

无法再像 Error、Array 和 Map 这样扩展内置对象。

建议,在任何 super(...) 调用后立即手动调整原型。

稍微修改 Lee Benson 原始答案对我有用。这还将 stackExtendableError 类的其他方法添加到实例中。

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
你还需要在MyError构造函数中调用Object.setPrototypeOf。https://dev59.com/ilgR5IYBdhLWcg3woebW#41102306 https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - CallMeLaNN

12

随着 Babel 6 的最新更改,我发现 transform-builtin-extend 已经不再起作用了。我最终使用了这种混合方法:

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;

import MyError from './MyError';

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

因此,所有这些测试都通过:

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

由于使用了super()调用,因此不需要使用this.stack = (new Error()).stack;技巧。

尽管上述代码在Babel中无法输出堆栈跟踪,除非调用this.stack = (new Error()).stack;Error.captureStackTrace(this, this.constructor.name);。在我看来,这可能是一个问题。

实际上,在Chrome控制台Node.js v4.2.1中,可以使用以下代码片段输出堆栈跟踪。

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

Chrome控制台的输出。

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)

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
除了@zangw的答案,你还可以像这样定义你的错误:

除了@zangw的答案,您还可以这样定义错误:

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

这将抛出正确的名称、消息和堆栈跟踪:

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

4
这个不起作用:new MyError('foo') instanceof MyError === false。意思是使用instanceof运算符判断一个实例对象是否为其自身的类型时,结果为假。 - Sukima
1
它在 Node.js v7.7.3 上运行。 - Gunar Gessner

3

我更喜欢比上面描述的更强的语法。在错误类型中添加其他方法将帮助您创建漂亮的console.log或其他内容。

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

为了测试这段代码,你可以运行类似下面的命令:

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

    console.warn(message);
}

欢迎扩展CustomError类型。可以向扩展类型添加一些特定功能或覆盖现有功能。例如:

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

我正在尝试使用ES6扩展Error

class MyError extends Error {…} 语法是正确的。

请注意,转译器仍然存在从内置对象继承的问题。在你的情况下,

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

看起来问题已经得到解决。

没错!但是消息仍未设置 - 我会写一个新的例子。 - Karel Bílek
我现在已经重写了这个例子。 - Karel Bílek
对我无效 - Karel Bílek
"super(m)"会返回一个空对象,显然Object.assign也无法帮助。 - Karel Bílek
@KarelBílek:你用的是什么浏览器?对我来说,Error.call()确实会返回一个新的错误实例。 - Bergi

2

鉴于此,接受的答案已不再适用,您可以使用工厂作为替代方法(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);


已被接受的答案对我仍然有效,如果你有必要的Babel插件。不过还是谢谢你的回答! - Karel Bílek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接