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

Posted

技术标签:

【中文标题】在 JavaScript 中扩展 Error 的好方法是啥?【英文标题】:What's a good way to extend Error in JavaScript?在 JavaScript 中扩展 Error 的好方法是什么? 【发布时间】:2010-11-25 19:25:54 【问题描述】:

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

在 Python 中,通常会继承 Exception。

在 JS 中应该做什么?

【问题讨论】:

【参考方案1】:

在 ES6 中:

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

source

【讨论】:

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

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

大多数环境设置了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 self-made Error object comparison。

【讨论】:

您可以将this.name = 'MyError'移出函数并将其更改为MyError.prototype.name = 'MyError' 这是这里唯一正确的答案,尽管出于风格问题,我可能会这样写。 function MyError(message) this.message = message; this.stack = Error().stack; MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError"; 我也要加MyError.prototype.constructor = MyError 在 ES6 Error.call(this, message);应该初始化this吧? MyError.prototype = Object.create(Error.prototype);【参考方案3】:

简而言之:

如果你使用的是 ES6没有转译器

class CustomError extends Error  /* ... */

如果您使用的是 Babel 转译器

选项 1:使用babel-plugin-transform-builtin-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的官方文档来看,你cannot extend any built-in JavaScript classes比如DateArrayDOM或者Error

这里描述了这个问题:

Native extends breaks htmlELement, Array, and others an object of The class which is extends by base type like Array,Number,Object,String or Error is not instanceof this class

其他 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

【讨论】:

class CustomError extends Error /* ... */ 不能正确处理特定于供应商的参数(lineNumber 等),“使用 ES6 语法扩展 Javascript 中的错误”是 Babel 特定的,您的 ES5 解决方案使用 const 并且它不处理自定义参数。 非常完整的答案。 这实际上提供了最全面的解决方案,并解释了为什么需要各种部件。非常感谢 JBE! 这帮助我解决了从“错误”继承的问题。这是一个两个小时的噩梦! 值得注意的是,console.log(new CustomError('test') instanceof CustomError);// false 的问题在撰写本文时是正确的,但现在已经解决。事实上the issue linked in the answer 已经解决了,我们可以测试here 的正确行为,并将代码粘贴到REPL 中,看看它是如何正确转译以使用正确的原型链进行实例化的。【参考方案4】:

编辑:请阅读 cmets。事实证明这只适用于 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

Original post on "Show me the code !"

短版:

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.constructor 替换为UserError,这样您就可以将代码移到函数外部,因此它只会被调用一次。

如果你走这条路,请确保在第一次抛出 UserError 时调用该行之前

该警告不适用于函数,因为无论顺序如何,函数都是首先创建的。因此,您可以毫无问题地将函数移至文件末尾。

浏览器兼容性

可在 Firefox 和 Chrome(以及 Node.JS)中运行,并履行所有承诺。

Internet Explorer 在以下情况下失败

错误没有err.stack开头,所以“这不是我的错”。

Error.captureStackTrace(this, this.constructor) 不存在,因此您需要执行其他操作,例如

if(Error.captureStackTrace) // AKA if not IE
    Error.captureStackTrace(this, this.constructor)

当你继承Error 时,toString 将不复存在。所以你也需要添加。

else
    this.toString = function ()  return this.name + ': ' + this.message 

IE 不会将UserError 视为instanceof Error,除非您在throw UserError 之前运行以下一段时间

UserError.prototype = Error.prototype

【讨论】:

我不认为 Firefox 实际上有 captureStackTrace。它是一个 V8 扩展,在 Firefox 中对我来说是未定义的,我也无法在网络上找到任何支持它的 Firefox 参考。 (谢谢!) Error.call(this) 确实没有做任何事情,因为它返回错误而不是修改this UserError.prototype = Error.prototype 具有误导性。这不做继承,这使它们同一个类 我相信Object.setPrototypeOf(this.constructor.prototype, Error.prototype)this.constructor.prototype.__proto__ = Error.prototype 更受欢迎,至少对于当前的浏览器而言。 为什么不this.constructor.prototype = Object.create(Error.prototype);?似乎比使用 __proto__ 更干净【参考方案5】:

为了避免样板文件针对每种不同类型的错误,我将一些解决方案的智慧结合到了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;这一行吗? @PeterTseng 因为name 已经设置在原型上,所以不再需要了。我删除了它。谢谢!【参考方案6】:

2018,我认为这是最好的方式;支持 IE9+ 和现代浏览器。

更新:请参阅 this test 和 repo 以比较不同的实现。

function CustomError(message) 
    Object.defineProperty(this, 'name', 
        enumerable: false,
        writable: false,
        value: 'CustomError'
    );

    Object.defineProperty(this, 'message', 
        enumerable: false,
        writable: true,
        value: message
    );

    if (Error.hasOwnProperty('captureStackTrace'))  // V8
        Error.captureStackTrace(this, CustomError);
     else 
        Object.defineProperty(this, 'stack', 
            enumerable: false,
            writable: false,
            value: (new Error(message)).stack
        );
    


if (typeof Object.setPrototypeOf === 'function') 
    Object.setPrototypeOf(CustomError.prototype, Error.prototype);
 else 
    CustomError.prototype = Object.create(Error.prototype, 
        constructor:  value: CustomError 
    );

还要注意__proto__ 属性是deprecated,它在其他答案中被广泛使用。

【讨论】:

你为什么使用setPrototypeOf()?至少根据 MDN,如果您可以通过在构造函数上设置 .prototype 属性来完成相同的事情,通常不鼓励使用它(就像您在 else 块中为没有 @ 的浏览所做的那样987654331@). 不鼓励更改对象的原型,而不是setPrototypeOf。但是如果你仍然需要它(正如 OP 所要求的那样),你应该使用内置的方法。正如 MDN 所指出的,这被认为是设置对象原型的正确方法。换句话说,MDN 说不要更改原型(因为它会影响性能和优化),但如果必须,请使用 setPrototypeOf 我的意思是,我认为您实际上不需要在这里更改原型。您可以简单地使用底部的行 (CustomError.prototype = Object.create(Error.prototype))。此外,Object.setPrototypeOf(CustomError, Error.prototype) 正在设置构造函数本身的原型,而不是为 CustomError 的新实例指定原型。无论如何,在 2016 年我认为实际上有更好的方法来扩展错误,尽管我仍在研究如何将它与 Babel 一起使用:github.com/loganfsmyth/babel-plugin-transform-builtin-extend/… CustomError.prototype = Object.create(Error.prototype) 也在改变原型。您必须更改它,因为 ES5 中没有内置的扩展/继承逻辑。我敢肯定你提到的 babel 插件会做类似的事情。 我创建了一个要点,说明为什么在这里使用 Object.setPrototypeOf 没有意义,至少在您使用它的方式上没有意义:gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348。也许您打算写Object.setPrototypeOf(CustomError.prototype, Error.prototype) - 这会更有意义(尽管仍然比简单地设置CustomError.prototype 没有任何好处)。【参考方案7】:

Crescent Fresh 的回答被高度投票的回答具有误导性。尽管他的警告无效,但还有其他一些限制他没有解决。

首先,Crescent 的“警告:”段落中的推理没有意义。这种解释意味着与多个 catch 语句相比,编码“一堆 if (error instanceof MyError) else ...”在某种程度上是繁琐或冗长的。单个 catch 块中的多个 instanceof 语句与多个 catch 语句一样简洁——干净简洁的代码,没有任何技巧。这是模拟 Java 出色的 throwable-subtype-specific 错误处理的好方法。

WRT“似乎没有设置子类的消息属性”,如果您使用正确构造的错误子类,情况并非如此。要创建自己的 ErrorX 错误子类,只需复制以“var MyError =”开头的代码块,将一个单词“MyError”更改为“ErrorX”。 (如果您想向子类添加自定义方法,请按照示例文本进行操作)。

JavaScript 错误子类化的真正重要限制是,对于跟踪和报告堆栈跟踪和实例化位置的 JavaScript 实现或调试器,如 FireFox,您自己的错误子类实现中的位置将被记录为实例化类的点,而如果您使用直接错误,它将是您运行“新错误(...)”的位置)。 IE 用户可能永远不会注意到,但在 FF 上使用 Fire Bug 的用户会看到与这些错误一起报告的无用文件名和行号值,并且必须深入到元素 #1 的堆栈跟踪以找到真正的实例化位置。

【讨论】:

我做对了吗 - 如果你不直接使用新的 Error(...) 子类,那么文件名和行被正确报告了吗?而且您基本上说在实践中(真实的,而不仅仅是性感或装饰性的)子类化错误,没有意义? 这个答案有点令人困惑,因为Crescent Fresh's 已被删除! 还是这样吗? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 行号是 2 不是调用 new 的地方【参考方案8】:

为了完整起见——只是因为之前的答案都没有提到这种方法——如果你正在使用 Node.js 并且不必关心浏览器的兼容性,那么使用它很容易达到预期的效果util 模块 (official docs here) 的内置 inherits

例如,假设您要创建一个自定义错误类,该类将错误代码作为第一个参数,将错误消息作为第二个参数:

文件custom-error.js

'use strict';

var util = require('util');

function CustomError(code, message) 
  Error.captureStackTrace(this, CustomError);
  this.name = CustomError.name;
  this.code = code;
  this.message = message;


util.inherits(CustomError, Error);

module.exports = CustomError;

现在您可以实例化并传递/抛出您的CustomError

var CustomError = require('./path/to/custom-error');

// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));

// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');

请注意,使用此 sn-p,堆栈跟踪将具有正确的文件名和行,并且错误实例将具有正确的名称!

这是因为使用了captureStackTrace 方法,该方法在目标对象上创建了一个stack 属性(在这种情况下,CustomError 被实例化)。有关其工作原理的更多详细信息,请查看文档here。

【讨论】:

this.message = this.message; 这是错的还是还有我不知道的关于 JS 的疯狂事情? 嘿@Alex,你完全正确!现在已经修好了。谢谢!【参考方案9】:

正如一些人所说,使用 ES6 相当容易:

class CustomError extends Error  

所以我在我的应用程序(Angular,Typescript)中尝试了它,但它没有工作。一段时间后,我发现问题来自 Typescript:O

见https://github.com/Microsoft/TypeScript/issues/13965

这很令人不安,因为如果你这样做:

class CustomError extends Error 
​

try 
  throw new CustomError()
 catch(e) 
  if (e instanceof CustomError) 
    console.log('Custom error');
   else 
    console.log('Basic error');
  

在节点中或直接在浏览器中显示:Custom error

尝试在 Typescript 操场上的项目中使用 Typescript 运行它,它会显示 Basic error...

解决方法如下:

class CustomError extends Error 
  // we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
  // otherwise we cannot use instanceof later to catch a given type
  public __proto__: Error;

  constructor(message?: string) 
    const trueProto = new.target.prototype;
    super(message);

    this.__proto__ = trueProto;
  

【讨论】:

这是否意味着在NodeJS上下文中是否需要这种解决方法取决于tsconfig.json中的target设置?如果目标是es5,则需要它,因为否则转译会丢失正确的原型信息。如果目标是es6 和更新的,转译保持class 语法,NodeJS 将按预期处理它而没有任何变通办法? 不确定,您必须对其进行测试;) 是的,我只能在使用target: "es5"时重现这个问题。 似乎在 TS 4.3.5 中按预期工作:typescriptlang.org/play?#code/… 为了澄清@maxime1992 的省时评论,ES3ES5 失败; ES2015 上的任何内容都按预期保留了 CustomError 原型。当然,CustomError 也是 instanceof Error,所以测试应该测试最具体的类。【参考方案10】:

这个解决方案怎么样?

而不是使用以下方法抛出您的自定义错误:

throw new MyError("Oops!");

你可以包装 Error 对象(有点像装饰器):

throw new MyError(Error("Oops!"));

这可以确保所有属性都是正确的,例如堆栈、文件名 lineNumber 等等。

然后,您所要做的就是复制属性,或者为它们定义 getter。 以下是使用 getter (IE9) 的示例:

function MyError(wrapped)

        this.wrapped = wrapped;
        this.wrapped.name = 'MyError';


function wrap(attr)

        Object.defineProperty(MyError.prototype, attr, 
                get: function()
                
                        return this.wrapped[attr];
                
        );


MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');

MyError.prototype.toString = function()

        return this.wrapped.toString();
;

【讨论】:

我已将此解决方案作为 npm 包发布:npmjs.com/package/throwable 非常优雅的解决方案,感谢分享!一种变体:new MyErr (arg1, arg2, new Error()) 在 MyErr 构造函数中,我们使用 Object.assign 将最后一个 arg 的属性分配给 this 我喜欢这个。您可以通过使用封装而不是继承来绕过限制。【参考方案11】:

我的解决方案比提供的其他答案更简单,并且没有缺点。

它保留了 Error 原型链和 Error 上的所有属性,而不需要它们的具体知识。它已经在 Chrome、Firefox、Node 和 IE11 中进行了测试。

唯一的限制是调用堆栈顶部的额外条目。但这很容易被忽略。

这是一个包含两个自定义参数的示例:

function CustomError(message, param1, param2) 
    var err = new Error(message);
    Object.setPrototypeOf(err, CustomError.prototype);

    err.param1 = param1;
    err.param2 = param2;

    return err;


CustomError.prototype = Object.create(
    Error.prototype,
    name: value: 'CustomError', enumerable: false
);

示例用法:

try 
    throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
 catch (ex) 
    console.log(ex.name); //CustomError
    console.log(ex.message); //Something Unexpected Happened!
    console.log(ex.param1); //1234
    console.log(ex.param2); //neat
    console.log(ex.stack); //stacktrace
    console.log(ex instanceof Error); //true
    console.log(ex instanceof CustomError); //true

对于需要 setPrototypeOf 的 polyfil 的环境:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) 
    obj.__proto__ = proto;
    return obj;
;

【讨论】:

如我的回答中所述,此解决方案可能会导致 Firefox 或其他仅在控制台中记录堆栈跟踪的第一行的浏览器出现问题 这是我发现的唯一适用于 ES5 的答案(使用 ES6 类也适用)。 Chromium DevTools 中的错误显示比其他答案更好。 注意:如果你在 TypeScript 中使用这个解决方案,你必须运行 throw CustomError('err') 而不是 throw new CustomError('err')【参考方案12】:

在上面的例子中,Error.apply(也是Error.call)对我没有任何帮助(Firefox 3.6/Chrome 5)。我使用的解决方法是:

function MyError(message, fileName, lineNumber) 
    var err = new Error();

    if (err.stack) 
        // remove one stack level:
        if (typeof(Components) != 'undefined') 
            // Mozilla:
            this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
        
        else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') 
            // Google Chrome/Node.js:
            this.stack = err.stack.replace(/\n[^\n]*/,'');
        
        else 
            this.stack = err.stack;
        
    
    this.message    = message    === undefined ? err.message    : message;
    this.fileName   = fileName   === undefined ? err.fileName   : fileName;
    this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;


MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';

【讨论】:

【参考方案13】:

正如其他人所说,在 Node 中,这很简单:

class DumbError extends Error 
    constructor(foo = 'bar', ...params) 
        super(...params);

        if (Error.captureStackTrace) 
            Error.captureStackTrace(this, DumbError);
        

        this.name = 'DumbError';

        this.foo = foo;
        this.date = new Date();
    


try 
    let x = 3;
    if (x < 10) 
        throw new DumbError();
    
 catch (error) 
    console.log(error);

【讨论】:

【参考方案14】:

我不喜欢所有其他答案,太长、太复杂或没有正确跟踪堆栈。这是我的方法,如果您需要更多自定义道具,请将它们传递给构造函数并将它们设置为名称。

class CustomError extends Error 
  constructor (message) 
    super(message)

    // needed for CustomError instanceof Error => true
    Object.setPrototypeOf(this, new.target.prototype);

    // Set the name
    this.name = this.constructor.name

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) 
      Error.captureStackTrace(this, this.constructor)
    
  


// create own CustomError sub classes
class SubCustomError extends CustomError

// Tests
console.log(new SubCustomError instanceof CustomError) // true
console.log(new SubCustomError instanceof CustomError) // true 
console.log(new CustomError instanceof Error) // true
console.log(new SubCustomError instanceof Error) // true

throw new SubCustomError ('test error')

【讨论】:

唯一有效的答案,在部门。【参考方案15】:

我只是想补充一下其他人已经说过的内容:

为确保自定义错误类在堆栈跟踪中正确显示,您需要将自定义错误类的原型的名称属性设置为自定义错误类的名称属性。 这就是我的意思:

CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';

所以完整的例子是:

    var CustomError = function(message) 
        var err = new Error(message);
        err.name = 'CustomError';
        this.name = err.name;
        this.message = err.message;
        //check if there is a stack property supported in browser
        if (err.stack) 
            this.stack = err.stack;
        
        //we should define how our toString function works as this will be used internally
        //by the browser's stack trace generation function
        this.toString = function() 
           return this.name + ': ' + this.message;
        ;
    ;
    CustomError.prototype = new Error();
    CustomError.prototype.name = 'CustomError';

当一切都说完了,你抛出你的新异常,它看起来像这样(我懒洋洋地在 chrome 开发工具中尝试了这个):

CustomError: Stuff Happened. GASP!
    at Error.CustomError (<anonymous>:3:19)
    at <anonymous>:2:7
    at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
    at Object.InjectedScript.evaluate (<anonymous>:481:21)

【讨论】:

这不会覆盖 ALL 错误实例的 name 属性吗? @panzi 你是对的。我已经修复了我的小错误。感谢您的提醒!【参考方案16】:

我的 2 美分:

为什么是另一个答案?

a)因为访问 Error.stack 属性(如在某些答案中)会导致很大的性能损失。

b) 因为只有一行。

c) 因为https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error 的解决方案似乎没有保留堆栈信息。

//MyError class constructor
function MyError(msg)
    this.__proto__.__proto__ = Error.apply(null, arguments);
;

使用示例

http://jsfiddle.net/luciotato/xXyeB/

它有什么作用?

this.__proto__.__proto__MyError.prototype.__proto__,因此它为所有实例设置__proto__ MyError 到特定的新创建的错误。它保留了 MyError 类的属性和方法,并将新的 Error 属性(包括 .stack)放入 __proto__ 链中。

明显的问题:

您不能拥有多个具有有用堆栈信息的 MyError 实例。

如果您不完全了解 this.__proto__.__proto__= 的作用,请勿使用此解决方案。

【讨论】:

【参考方案17】:

2021 年更新

除了标准的message 属性之外,JavaScript 现在支持将特定的错误cause 作为可选参数添加到Error 构造函数:

const error1 = new Error('Error one');
const error2 = new Error('Error two',  cause: error1 );
// error2.cause === error1
在Node v16.9.0 中提供。 在 Chrome、Firefox 和 Safari 中可用(有关版本,请参阅 browser compatibility)

【讨论】:

【参考方案18】:

由于 JavaScript 异常很难子类化,我不子类化。我只是创建一个新的异常类并在其中使用一个错误。我更改了 Error.name 属性,使其看起来像控制台上的自定义异常:

var InvalidInputError = function(message) 
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
;

上面的新异常可以像常规错误一样抛出,它会按预期工作,例如:

throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string 

警告:堆栈跟踪并不完美,因为它会将您带到创建新错误的位置,而不是您抛出的位置。这在 Chrome 上没什么大不了的,因为它直接在控制台中为您提供了完整的堆栈跟踪。但例如在 Firefox 上问题更大。

【讨论】:

这在m = new InvalidInputError(); dontThrowMeYet(m);的情况下失败 @Eric 我同意,但这似乎是一个很小的限制。我从来不需要提前实例化异常对象(元编程用途除外,如我上面的代码示例)。这对你来说真的是个问题吗? 是的,行为似乎是一样的,所以我会改变我的答案。我对堆栈跟踪不是 100% 满意,它会将您带到 Firefox 和 Chrome 上的“var error”行 @JonathanBenn 我真的迟到了,所以也许你已经知道了。当我使用异步编程和 Promises 时,我经常实例化一个异常对象。在@Eric 的名字之后,我经常使用m = new ... 然后Promise.reject(m)。没必要,但代码更容易阅读。 @JonathanBenn:(呵呵)在 10 月 14 日,您似乎认为在抛出异常对象之前实例化它是罕见的。我举了一个我做过的例子。我不会说它很常见,但是当我需要它时它很方便。而且,我的代码更具可读性,因为实例化全部在一行上,而拒绝全部在另一行上。我希望这样做!【参考方案19】:

正如 Mohsen 的回答中所指出的,在 ES6 中,可以使用类来扩展错误。这要容易得多,并且它们的行为与本机错误更一致……但不幸的是,如果您需要支持 ES6 之前的浏览器,在浏览器中使用它并不是一件简单的事情。有关如何实施的一些说明,请参见下文,但与此同时,我建议采用一种相对简单的方法,该方法结合了其他答案中的一些最佳建议:

function CustomError(message) 
    //This is for future compatibility with the ES6 version, which
    //would display a similar message if invoked without the
    //`new` operator.
    if (!(this instanceof CustomError)) 
        throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
    
    this.message = message;

    //Stack trace in V8
    if (Error.captureStackTrace) 
       Error.captureStackTrace(this, CustomError);
    
    else this.stack = (new Error).stack;

CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';

在 ES6 中很简单:

class CustomError extends Error 

...您可以使用try eval('class X') 检测对 ES6 类的支持,但是如果您尝试在旧浏览器加载的脚本中包含 ES6 版本,则会出现语法错误。因此,支持所有浏览器的唯一方法是为支持 ES6 的浏览器动态加载单独的脚本(例如通过 AJAX 或 eval())。更复杂的是,并非所有环境都支持eval()(由于内容安全策略),这可能是您项目的考虑因素,也可能不是。

所以现在,无论是上面的第一种方法,还是直接使用Error 而不尝试扩展它似乎都是对于需要支持非 ES6 浏览器的代码实际上可以做到的最好的方法。

有些人可能会考虑另一种方法,即使用Object.setPrototypeOf() 创建一个错误对象,该对象是您的自定义错误类型的一个实例,但看起来和行为更像是控制台(感谢Ben's answer 的推荐)。这是我对这种方法的看法:https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8。但考虑到有一天我们将能够只使用 ES6,我个人不确定这种方法的复杂性是否值得。

【讨论】:

【参考方案20】:

Mohsen 在上面的 ES6 中有一个很好的答案来设置名称,但是如果你使用 TypeScript 或者你生活在未来,希望这个 proposal for public and private class fields 已经作为提案通过第 3 阶段并进入第 4 阶段作为 ECMAScript/JavaScript 的一部分,那么您可能想知道这只是稍微短一点。第 3 阶段是浏览器开始实现功能的地方,因此如果您的浏览器支持它,下面的代码可能会起作用。 (在新的 Edge 浏览器 v81 中测试它似乎工作正常)。请注意,尽管目前这是一个不稳定的功能,应谨慎使用,您应始终检查浏览器对不稳定功能的支持。这篇文章主要是针对那些浏览器可能支持的未来居民。要检查支持检查 MDN 和 Can I use。它目前在浏览器市场上获得了 66% 的支持,但不是那么好,所以如果你现在真的想使用它并且不想等待,可以使用像 Babel 这样的转译器或像 TypeScript 这样的东西。

class EOFError extends Error  
  name="EOFError"

throw new EOFError("Oops errored");

将此与抛出时不会记录其名称的无名错误进行比较。

class NamelessEOFError extends Error 
throw new NamelessEOFError("Oops errored");

【讨论】:

【参考方案21】:

这并不复杂,但我个人认为这是轻松扩展错误的最简单方法。

export default class ExtendableError extends Error 
    constructor(message) 
        super(message);
        this.name = this.constructor.name;
    

创建一个实用程序类,如所谓的ExtendableError。这个实用程序类的目的是和普通的Error类一样,但是把name属性默认改成类名,这样真的很容易扩展报错。

现在,如果你想扩展一个错误,只需要一行。

class MyError extends ExtendableError 

【讨论】:

【参考方案22】:

做到这一点的方法是从构造函数返回apply的结果,以及以通常复杂的javascripty方式设置原型:

function MyError() 
    var tmp = Error.apply(this, arguments);
    tmp.name = this.name = 'MyError'

    this.stack = tmp.stack
    this.message = tmp.message

    return this

    var IntermediateInheritor = function() 
        IntermediateInheritor.prototype = Error.prototype;
    MyError.prototype = new IntermediateInheritor()

var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error)                // true
console.log(myError instanceof MyError)              // true
console.log(myError.toString())                      // MyError: message
console.log(myError.stack)                           // MyError: message \n 
                                                     // <stack trace ...>

目前这种方式的唯一问题(我已经迭代了一点)是

stackmessage 以外的属性不包含在 MyError 和 stacktrace 有一个额外的行,这并不是真正需要的。

第一个问题可以通过使用此答案中的技巧迭代所有不可枚举的错误属性来解决:Is it possible to get the non-enumerable inherited property names of an object?,但这不受 ie

【讨论】:

我制作了一个可以扩展大多数常规旧javascript对象的模块,包括错误。在这一点上它已经相当成熟了github.com/fresheneesz/proto【参考方案23】:

sn-p 说明了一切。

function add(x, y) 
      if (x && y) 
        return x + y;
       else 
        /**
         * 
         * the error thrown will be instanceof Error class and InvalidArgsError also
         */
        throw new InvalidArgsError();
        // throw new Invalid_Args_Error(); 
      
    

    // Declare custom error using using Class
    class Invalid_Args_Error extends Error 
      constructor() 
        super("Invalid arguments");
        Error.captureStackTrace(this);
      
    

    // Declare custom error using Function
    function InvalidArgsError(message) 
      this.message = `Invalid arguments`;
      Error.captureStackTrace(this);
    
    // does the same magic as extends keyword
    Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);

    try
      add(2)
    catch(e)
      // true
      if(e instanceof Error)
        console.log(e)
      
      // true
      if(e instanceof InvalidArgsError)
        console.log(e)
      
    

【讨论】:

【参考方案24】:

我提出的解决方案是使用error的.name属性来区分错误类型而不是instancof

这并不能完全回答问题,但我认为对于某些情况来说,这是一个合理的解决方案。

我看到能够拥有instanceof CustomError 的好处是您可以在您的承诺捕获处理程序中进行自定义处理。

例如:

class CustomError extends Error /** ... **/

axios
  .post(url, payload)
  .then(data => 
    if (!data.loggedIn) throw CustomError("not logged in");
    return data;
  )
  .catch(error => 
    if (error instanceof CustomError) /** custom handling of error*//
    throw error
  )

如果这就是您想要完成的任务,那么.name 参数也非常适合您:

export const ERROR_NOT_LOGGED_IN = "ERROR_NOT_LOGGED_IN";

axios
  .post(url, payload)
  .then(data => 
    if (!data.loggedIn) throw Error("not logged in").name=ERROR_NOT_LOGGED_IN ;
    return data;
  )
  .catch(error => 
    if (error.name === ERROR_NOT_LOGGED_IN) /** custom handling of error*//
    throw error
  )

【讨论】:

【参考方案25】:

我会退后一步,考虑一下您为什么要这样做?我认为关键是要以不同的方式处理不同的错误。

例如,在 Python 中,您可以将 catch 语句限制为仅捕获 MyValidationError,也许您希望能够在 javascript 中做类似的事情。

catch (MyValidationError e) 
    ....

您不能在 javascript 中执行此操作。只会有一个 catch 块。您应该对错误使用 if 语句来确定其类型。

catch(e) if(isMyValidationError(e)) ... else // maybe rethrow? throw e;

我想我会改为抛出一个带有类型、消息和您认为合适的任何其他属性的原始对象。

throw  type: "validation", message: "Invalid timestamp" 

当你发现错误时:

catch(e) 
    if(e.type === "validation") 
         // handle error
    
    // re-throw, or whatever else

【讨论】:

扔一个物体不是个好主意。您没有error.stack,标准工具无法使用它等等。更好的方法是向错误实例添加属性,例如var e = new Error(); e.type = "validation"; ...【参考方案26】:

自定义错误装饰器

这是基于George Bailey's answer,但扩展和简化了最初的想法。它是用 CoffeeScript 编写的,但很容易转换为 JavaScript。这个想法是用包装它的装饰器扩展 Bailey 的自定义错误,让您轻松创建新的自定义错误。

注意:这仅适用于 V8。其他环境不支持Error.captureStackTrace

定义

装饰器获取错误类型的名称,并返回一个带有错误消息的函数,并包含错误名称。

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "# type Error: # message "

使用

现在创建新的错误类型很简单。

StorageError   = BaseError "Storage"
SignatureError = BaseError "Signature"

为了好玩,您现在可以定义一个函数,如果使用太多 args 调用它会抛出 SignatureError

f = -> throw SignatureError "too many args" if arguments.length

这已经过很好的测试,似乎可以在 V8 上完美运行,维护回溯、位置等。

注意:在构造自定义错误时,使用new 是可选的。

【讨论】:

【参考方案27】:

如果你不关心错误的表现,这是你能做的最小的事情

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) 
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error

你可以在没有新的 MyError(message) 的情况下使用它

通过在构造函数Error被调用后改变原型,我们不必设置调用栈和消息

【讨论】:

以上是关于在 JavaScript 中扩展 Error 的好方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 CodeIgniter 中寻找一些 Javascript 类的好例子

使用jQuery / Javascript(查询字符串)获取查询字符串参数url值

.Net 4 并行扩展的好学习资料

mongo存储的javascript是nodejs中多个数据库插入的好解决方案

检查文件扩展名是不是为图像的好方法

通过 ajax 加载 JavaScript 的好方法