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

Jos*_*son 340 javascript error-handling exception

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

在Python中,通常会有一个子类Exception.

在JS中做什么是合适的?

小智 202

唯一的标准字段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
Run Code Online (Sandbox Code Playgroud)

您可以嗅探堆栈,从中移除不需要的元素并提取fileName和lineNumber等信息,但这样做需要有关当前正在运行的JavaScript平台的信息.大多数情况是不必要的 - 如果你真的想要,你可以在验尸中做到.

Safari是一个值得注意的例外.没有stack属性,但是抛出的对象的throw关键字集sourceURLline属性.这些事情保证是正确的.

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

  • 这是唯一正确的答案,虽然作为一种风格,我可能会这样写.`function MyError(message){this.message = message; this.stack = Error().stack; MyError.prototype = Object.create(Error.prototype); MyError.prototype.name ="MyError";` (34认同)
  • 您可以在函数外部移动`this.name ='MyError'`并将其更改为`MyError.prototype.name ='MyError'. (19认同)
  • 我也添加了'MyError.prototype.constructor = MyError`. (9认同)
  • MyError.prototype = Object.create(Error.prototype); (4认同)
  • 在ES6中Error.call(this,message); 应该初始化`this`,对吧? (3认同)
  • @Silverspur分配`MyError.prototype = Object.create(Error.prototype)`(或新错误)的行为将覆盖`MyError.prototype`的所有成员.因此,`MyError.prototype.constructor`实际上是`Error.prototype.constructor`,因为我们只是将整个`Error.prototype`分配给`MyError.prototype`.这是JavaScript的副作用,实际上不是面向对象的语言,而是原型语言.所以这就是为什么我们将`MyError.prototype.constructor = MyError`分配回MyError构造函数. (3认同)

Moh*_*sen 142

在ES6中:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}
Run Code Online (Sandbox Code Playgroud)

资源

  • 值得一提的是,如果您通过诸如Babel之类的转换器使用ES6功能,则这不起作用,因为子类必须扩展类. (46认同)
  • **为了便于维护**,请改用`this.name = this.constructor.name;`. (18认同)
  • @ K._但这不适用于缩小代码! (5认同)
  • 如果使用的是巴贝尔,并在节点> 5.x的,你不应该使用ES2015预设但https://www.npmjs.com/package/babel-preset-node5将允许您使用本机ES6延伸加上更多 (4认同)
  • @КонстантинВан 不幸的是,这只是一个没有类名缩小的选项。 (3认同)
  • 如果可能,这是最好的方法。自定义错误的行为更像是 Chrome 和 Firefox(可能还有其他浏览器)中的常规错误。 (2认同)
  • 对于浏览器,请注意您可以在运行时检测对类的支持,并相应地回退到非类版本.检测代码:`var supportsClasses = false; 试试{eval('class X {}'); supportsClasses = true;} catch(e){}` (2认同)
  • 这已经适用于最新的 babel 预设,他们修复了 instanceof 问题,并且缩小的错误可能是错误的缩小器配置 (2认同)

Bry*_*eld 43

编辑:请阅读评论.事实证明这只适用于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
}
Run Code Online (Sandbox Code Playgroud)

关于"给我看代码!"的原帖

精简版:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}
Run Code Online (Sandbox Code Playgroud)

我保留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)
    
    Run Code Online (Sandbox Code Playgroud)
  • toString子类时不再存在Error.所以你还需要添加.

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

    UserError.prototype = Error.prototype
    
    Run Code Online (Sandbox Code Playgroud)

  • 我不认为Firefox实际上有captureStackTrace.这是一个V8扩展,在我的Firefox中是未定义的,我也无法在网上找到支持它的Firefox的任何参考.(谢谢!) (15认同)
  • `Error.call(this)`确实没有做任何事情,因为它*返回*错误而不是修改`this`. (5认同)

JBE*_*JBE 42

简而言之:

选项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);
Run Code Online (Sandbox Code Playgroud)
  • 如果您使用的是纯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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 替代方案:使用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
Run Code Online (Sandbox Code Playgroud)

事实上,从巴贝尔的官方文档,你不能扩展任何内置的JavaScript类Date,Array,DOMError.

这个问题在这里描述:

其他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"}
Run Code Online (Sandbox Code Playgroud)

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

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,console.log(CustomError的新CustomError('test')instance); // false的问题在撰写本文时为true,但现已解决。实际上,[答案中链接的问题](https://github.com/babel/babel/issues/4480)已解决,我们可以在[此处]测试正确的行为(https://jsbin.com/piqozazike / edit?js,console),然后将代码粘贴到[REPL](https://babeljs.io/repl/)中,并查看如何正确地对其进行转译以使用正确的原型链进行实例化。 (2认同)

Rub*_*rgh 28

为了避免出现每种不同类型错误的样板,我将一些解决方案的智慧结合到一个  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;
}
Run Code Online (Sandbox Code Playgroud)

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

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';
});
Run Code Online (Sandbox Code Playgroud)


Onu*_*rım 26

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

更新:请参阅此测试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 }
    });
}
Run Code Online (Sandbox Code Playgroud)

还要注意,不推荐使用__proto__属性,这在其他答案中被广泛使用.


Vic*_*der 19

为了完整起见 - 只是因为之前的答案都没有提到这个方法 - 如果您正在使用Node.js并且不必关心浏览器兼容性,那么内置的内容很容易实现所需的效果inheritsutil模块(官方文档).

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

file 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;
Run Code Online (Sandbox Code Playgroud)

现在你可以实例化并传递/抛出你的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!');
Run Code Online (Sandbox Code Playgroud)

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

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


Bla*_*ine 18

Crescent Fresh回答高度评价的回答是误导性的.虽然他的警告是无效的,但他还没有解决其他限制.

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

WRT"出现子类的message属性未设置",如果使用正确构造的Error子类则不是这种情况.要创建自己的ErrorX Error子类,只需复制以"var MyError ="开头的代码块,将单词"MyError"更改为"ErrorX".(如果要向子类添加自定义方法,请遵循示例文本).

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

  • 这个答案有些令人困惑,因为"Crescent Fresh's"已被删除! (3认同)

t_d*_*m93 15

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

const error1 = new Error('Error one');
const error2 = new Error('Error two', { cause: error1 });
// error2.cause === error1
Run Code Online (Sandbox Code Playgroud)

  • @DanielValencia 当您以 ES2022(或更高版本)为目标时,从 TypeScript 4.6 开始支持它 (4认同)

JoW*_*Wie 12

这个解决方案怎么样?

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

throw new MyError("Oops!");
Run Code Online (Sandbox Code Playgroud)

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

throw new MyError(Error("Oops!"));
Run Code Online (Sandbox Code Playgroud)

这可以确保所有属性都正确,例如stack,fileName 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();
};
Run Code Online (Sandbox Code Playgroud)


SeP*_*PeF 12

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

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')
Run Code Online (Sandbox Code Playgroud)

  • `Object.setPrototypeOf(this, new.target.prototype)` 真的有必要吗?我正在关注 Corman 的[这个问题的答案](/sf/answers/4814315061/),它没有 `Object.setProto...` 行,我的自定义错误是其自身的实例 (`err1 instanceOf MyError`) 及其父实例 (`err1 instanceOf Error`)。 (3认同)

Ben*_*Ben 9

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

它保留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}}
);
Run Code Online (Sandbox Code Playgroud)

用法示例:

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
}
Run Code Online (Sandbox Code Playgroud)

对于需要setPrototypeOf的polyfil的环境:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};
Run Code Online (Sandbox Code Playgroud)


pan*_*nzi 8

在上面的例子中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';
Run Code Online (Sandbox Code Playgroud)


max*_*992 7

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

class CustomError extends Error { }
Run Code Online (Sandbox Code Playgroud)

所以我在我的应用程序(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');
  }
}
Run Code Online (Sandbox Code Playgroud)

在节点中或直接进入浏览器,它将显示: 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,我只能在使用 `target: "es5"` 时重现此问题。 (2认同)

Cor*_*man 7

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

export default class ExtendableError extends Error {
    constructor(message) {
        super(message);
        this.name = this.constructor.name;
    }
}
Run Code Online (Sandbox Code Playgroud)

创建一个类似于所谓的实用程序类ExtendableError。这个实用程序类的目的是像普通的Error类一样,但name默认将属性更改为类的名称,因此很容易扩展错误。

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

class MyError extends ExtendableError {}
Run Code Online (Sandbox Code Playgroud)


Muh*_*mer 6

正如其他人所说,在 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);
}
Run Code Online (Sandbox Code Playgroud)