在 JavaScript 中扩展 Error 的好方法是什么?

IT技术 javascript exception error-handling
2021-02-01 01:07:11

我想在我的 JS 代码中抛出一些东西,我希望它们成为 instanceof Error,但我也想让它们成为其他东西。

在 Python 中,通常会继承 Exception。

在 JS 中做什么合适?

6个回答

Error 对象唯一的标准字段是message属性。(参见MDN或 EcmaScript 语言规范,第 15.11 节)其他一切都是特定于平台的。

Mosts环境设置stack属性,但fileNamelineNumber实际上是无用的继承使用。

因此,简约的方法是:

function MyError(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = (new Error()).stack;
}
MyError.prototype = new Error;  // <-- remove this if you do not 
                                //     want MyError to be instanceof Error

您可以嗅探堆栈,从中移除不需要的元素并提取文件名和行号等信息,但这样做需要有关 JavaScript 当前运行平台的信息。大多数情况是不必要的——如果你真的想要,你可以在事后做。

Safari是一个明显的例外。没有stack属性,但是throw关键字集sourceURLline被抛出的对象的属性。这些东西保证是正确的。

我使用的测试用例可以在这里找到:JavaScript自制的错误对象比较

您可以移动this.name = 'MyError' 函数外部并将其更改为MyError.prototype.name = 'MyError'.
2021-03-14 01:07:11
这是这里唯一正确的答案,尽管就风格而言,我可能会这样写。 function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
2021-03-17 01:07:11
我也要补充MyError.prototype.constructor = MyError
2021-03-18 01:07:11
在 ES6 中 Error.call(this, message); 应该初始化this,对吗?
2021-03-26 01:07:11
MyError.prototype = Object.create(Error.prototype);
2021-04-10 01:07:11

在 ES6 中:

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

来源

@КонстантинВан 不幸的是,这只是一个没有类名缩小的选项。
2021-03-18 01:07:11
如果您使用 babel 并且在 node > 5.x 上,则不应使用 es2015 预设,但npmjs.com/package/babel-preset-node5将允许您使用原生 es6 扩展以及更多
2021-03-30 01:07:11
为便于维护,请this.name = this.constructor.name;改用。
2021-04-02 01:07:11
值得一提的是,如果您通过转译器(例如 Babel)使用 ES6 功能,这将不起作用,因为子类必须扩展类。
2021-04-05 01:07:11
如果可能,这是最好的方法。自定义错误的行为更像是 Chrome 和 Firefox(可能还有其他浏览器)中的常规错误。
2021-04-06 01:07:11

简而言之:

  • 如果您使用没有转译器的 ES6

    class CustomError extends Error { /* ... */}
    
  • 如果您使用的是Babel 转译器

选项 1:使用babel-plugin-transform-b​​uiltin-extend

选项 2:自己做(灵感来自同一个库)

    function CustomError(...args) {
      const instance = Reflect.construct(Error, args);
      Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Reflect.setPrototypeOf(CustomError, Error);
  • 如果您使用的是纯 ES5

    function CustomError(message, fileName, lineNumber) {
      var instance = new Error(message, fileName, lineNumber);
      Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    if (Object.setPrototypeOf){
        Object.setPrototypeOf(CustomError, Error);
    } else {
        CustomError.__proto__ = Error;
    }
    
  • 替代方案:使用Classtrophobic框架

解释:

为什么使用 ES6 和 Babel 扩展 Error 类是个问题?

因为不再像这样识别 CustomError 的实例。

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

实际上,从 Babel 的官方文档来看,您不能扩展任何内置的 JavaScript 类,例如DateArrayDOMError

此处描述了该问题:

其他 SO 答案呢?

所有给定的答案都解决了instanceof问题,但您会丢失常规错误console.log

console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵    at CustomError (<anonymous>:4:19)↵    at <anonymous>:1:5"}

而使用上述方法,不仅可以解决instanceof问题,还可以保留常规错误console.log

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
这帮助我解决了从“错误”继承的问题。这是两个小时的噩梦!
2021-03-13 01:07:11
class CustomError extends Error { /* ... */}无法正确处理特定于供应商的参数(lineNumber等),“使用 ES6 语法扩展 Javascript 中的错误”是 Babel 特定的,您的 ES5 解决方案使用const并且不处理自定义参数。
2021-03-20 01:07:11
值得注意的是,在console.log(new CustomError('test') instanceof CustomError);// false撰写本文时,问题是真实的,但现在已经解决。事实上,答案中链接的问题已经解决,我们可以在这里测试正确的行为并通过将代码粘贴到REPL 中并查看它如何正确转换为使用正确的原型链进行实例化。
2021-03-24 01:07:11
非常完整的答案。
2021-04-05 01:07:11
这实际上提供了最全面的解决方案,并解释了为什么需要各个部分。非常感谢JBE!
2021-04-08 01:07:11

编辑:请阅读评论。事实证明,这只适用于 V8 (Chrome / Node.JS) 我的意图是提供一个跨浏览器的解决方案,它适用于所有浏览器,并在有支持的地方提供堆栈跟踪。

编辑:我制作了这个社区 Wiki 以允许进行更多编辑。

适用于 V8 (Chrome / Node.JS) 的解决方案,适用于 Firefox,并且可以修改为在 IE 中基本正常运行。(见文末)

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
  Error.call(this) // Does not seem necessary. Perhaps remove this line?
  Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
  this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
  this.message = message; // Used to set the message
}

关于“给我看代码!”的原始帖子

简洁版本:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

我保留this.constructor.prototype.__proto__ = Error.prototype在函数内部以将所有代码保存在一起。但是你也可以this.constructorUserErrorand替换这允许你将代码移到函数之外,所以它只被调用一次。

如果您走那条路线,请确保在第一次 throw之前调用该线路UserError

该警告不适用于该函数,因为无论顺序如何,首先创建函数。因此,您可以将该函数移动到文件末尾,而不会出现问题。

浏览器兼容性

适用于 Firefox 和 Chrome(以及 Node.JS)并实现所有Promise。

Internet Explorer 在以下情况下失败

  • 错误不一定err.stack要开始,所以“这不是我的错”。

  • Error.captureStackTrace(this, this.constructor) 不存在所以你需要做一些其他的事情

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
    
  • toString当你子类化时不复存在Error所以你还需要添加。

    else
        this.toString = function () { return this.name + ': ' + this.message }
    
  • IE 不会考虑UserError是一个,instanceof Error除非你在你之前运行以下一段时间throw UserError

    UserError.prototype = Error.prototype
    
UserError.prototype = Error.prototype是误导。这不会进行继承,这使它们成为同一个类
2021-03-16 01:07:11
为什么不this.constructor.prototype = Object.create(Error.prototype);呢?看起来比使用更干净__proto__
2021-04-01 01:07:11
Error.call(this)确实没有做任何事情,因为它返回错误而不是修改this.
2021-04-03 01:07:11
我相信至少对于当前的浏览器来说Object.setPrototypeOf(this.constructor.prototype, Error.prototype)是首选this.constructor.prototype.__proto__ = Error.prototype
2021-04-04 01:07:11
我认为 Firefox 实际上没有 captureStackTrace。它是一个 V8 扩展,在 Firefox 中对我来说是未定义的,我也无法在网络上找到任何支持它的 Firefox 参考。(不过还是谢谢!)
2021-04-10 01:07:11

为了避免每种不同类型错误的样板文件,我将一些解决方案的智慧组合成一个 createErrorType函数:

function createErrorType(name, init) {
  function E(message) {
    if (!Error.captureStackTrace)
      this.stack = (new Error()).stack;
    else
      Error.captureStackTrace(this, this.constructor);
    this.message = message;
    init && init.apply(this, arguments);
  }
  E.prototype = new Error();
  E.prototype.name = name;
  E.prototype.constructor = E;
  return E;
}

然后您可以轻松定义新的错误类型,如下所示:

var NameError = createErrorType('NameError', function (name, invalidChar) {
  this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});

var UnboundError = createErrorType('UnboundError', function (variableName) {
  this.message = 'Variable ' + variableName + ' is not bound';
});
你还有什么理由需要这条线this.name = name;吗?
2021-03-27 01:07:11
@PeterTseng 由于name已经在原型上设置,不再需要了。我删除了它。谢谢!
2021-04-03 01:07:11