简体   繁体   English

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

[英]What's a good way to extend Error in JavaScript?

I want to throw some things in my JS code and I want them to be instanceof Error, but I also want to have them be something else.我想在我的 JS 代码中抛出一些东西,我希望它们是 instanceof Error,但我也想让它们成为其他东西。

In Python, typically, one would subclass Exception.在 Python 中,通常会继承 Exception。

What's the appropriate thing to do in JS?什么是在 JS 中做的合适的事情?

The only standard field Error object has is the message property. Error 对象唯一的标准字段是message属性。 (See MDN , or EcmaScript Language Specification, section 15.11) Everything else is platform specific. (请参阅MDN或 EcmaScript 语言规范,第 15.11 节)其他一切都是特定于平台的。

Mosts environments set the stack property, but fileName and lineNumber are practically useless to be used in inheritance.大多数环境设置了stack属性,但是fileNamelineNumber在继承中实际上没有用。

So, the minimalistic approach is:因此,简约的方法是:

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

You could sniff the stack, unshift unwanted elements from it and extract information like fileName and lineNumber, but doing so requires information about the platform JavaScript is currently running upon.您可以嗅探堆栈,从中移除不需要的元素并提取文件名和行号等信息,但这样做需要有关 JavaScript 当前运行平台的信息。 Most cases that is unnecessary -- and you can do it in post-mortem if you really want.大多数情况下是不必要的——如果你真的想要,你可以在事后分析。

Safari is a notable exception. Safari是一个明显的例外。 There is no stack property, but the throw keyword sets sourceURL and line properties of the object that is being thrown.没有stack属性,但throw关键字设置了被抛出对象的sourceURLline属性。 Those things are guaranteed to be correct.这些东西保证是正确的。

Test cases I used can be found here: JavaScript self-made Error object comparison .我使用的测试用例可以在这里找到: JavaScript自制的错误对象比较

In ES6:在 ES6 中:

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

source 来源

In short:简而言之:

  • If you are using ES6 without transpilers :如果您使用没有转译器的 ES6:

     class CustomError extends Error { /* ... */}
  • If you are using Babel transpiler :如果您使用的是Babel 转译器

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

Option 2: do it yourself (inspired from that same library)选项 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);
  • If you are using pure ES5 :如果您使用纯 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; }
  • Alternative: use Classtrophobic framework替代方案:使用Classtrophobic框架

Explanation:解释:

Why extending the Error class using ES6 and Babel is a problem?为什么使用 ES6 和 Babel 扩展 Error 类是个问题?

Because an instance of CustomError is not anymore recognized as such.因为不再像这样识别 CustomError 的实例。

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

In fact, from the official documentation of Babel, you cannot extend any built-in JavaScript classes such as Date , Array , DOM or Error .事实上,从 Babel 的官方文档来看,你不能扩展任何内置的 JavaScript 类,例如DateArrayDOMError

The issue is described here:此处描述了该问题:

What about the other SO answers?其他 SO 答案呢?

All the given answers fix the instanceof issue but you lose the regular error console.log :所有给定的答案都解决了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"}

Whereas using the method mentioned above, not only you fix the instanceof issue but you also keep the regular error console.log :而使用上述方法,不仅可以解决instanceof问题,还可以保留常规错误console.log

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

Edit: Please read comments.编辑:请阅读评论。 It turns out this only works well in V8 (Chrome / Node.JS) My intent was to provide a cross-browser solution, which would work in all browsers, and provide stack trace where support is there.事实证明,这只适用于 V8 (Chrome / Node.JS) 我的意图是提供一个跨浏览器的解决方案,它适用于所有浏览器,并在有支持的地方提供堆栈跟踪。

Edit: I made this Community Wiki to allow for more editing.编辑:我制作了这个社区 Wiki 以允许进行更多编辑。

Solution for V8 (Chrome / Node.JS), works in Firefox, and can be modified to function mostly correctly in IE.适用于 V8 (Chrome / Node.JS) 的解决方案,适用于 Firefox,并且可以修改为在 IE 中基本正常运行。 (see end of post) (见文末)

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 !" 关于“给我看代码!”的原始帖子

Short version:精简版:

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

I keep this.constructor.prototype.__proto__ = Error.prototype inside the function to keep all the code together.我将this.constructor.prototype.__proto__ = Error.prototype保留在函数中以将所有代码保持在一起。 But you can also replace this.constructor with UserError and that allows you to move the code to outside the function, so it only gets called once.但是你也可以用UserError替换this.constructor ,这允许你将代码移到函数之外,所以它只被调用一次。

If you go that route, make sure you call that line before the first time you throw UserError .如果你走那条路,请确保在第一次抛出UserError之前调用该行。

That caveat does not apply the function, because functions are created first, no matter the order.该警告不适用于该函数,因为无论顺序如何,首先创建函数。 Thus, you can move the function to the end of the file, without a problem.因此,您可以将该函数移动到文件末尾,而不会出现问题。

Browser Compatibility浏览器兼容性

Works in Firefox and Chrome (and Node.JS) and fills all promises.适用于 Firefox 和 Chrome(以及 Node.JS)并实现所有承诺。

Internet Explorer fails in the following Internet Explorer 在以下情况下失败

  • Errors do not have err.stack to begin with, so "it's not my fault".错误没有err.stack开始,所以“这不是我的错”。

  • Error.captureStackTrace(this, this.constructor) does not exist so you need to do something else like Error.captureStackTrace(this, this.constructor)不存在所以你需要做一些其他的事情

    if(Error.captureStackTrace) // AKA if not IE Error.captureStackTrace(this, this.constructor)
  • toString ceases to exist when you subclass Error .当您继承Error时, toString不再存在。 So you also need to add.所以你还需要添加。

     else this.toString = function () { return this.name + ': ' + this.message }
  • IE will not consider UserError to be an instanceof Error unless you run the following some time before you throw UserError IE 不会将UserError视为一个instanceof Error除非您在throw UserError之前运行以下一段时间

    UserError.prototype = Error.prototype

To avoid the boilerplate for every different type of error, I combined the wisdom of some of the solutions into a createErrorType function:为了避免每种不同类型错误的样板文件,我将一些解决方案的智慧结合到一个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;
}

Then you can define new error types easily as follows:然后您可以轻松定义新的错误类型,如下所示:

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

In 2018 , I think this is the best way; 2018年,我认为这是最好的方式; that supports IE9+ and modern browsers.支持 IE9+ 和现代浏览器。

UPDATE : See this test and repo for comparison on different implementations.更新:请参阅此测试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 }
    });
}

Also beware that __proto__ property is deprecated which is widely used in other answers.还要注意__proto__属性已被弃用,这在其他答案中被广泛使用。

Crescent Fresh's answer highly-voted answer is misleading. Crescent Fresh 的回答高票回答具有误导性。 Though his warnings are invalid, there are other limitations that he doesn't address.尽管他的警告无效,但还有其他限制他没有解决。

First, the reasoning in Crescent's "Caveats:" paragraph doesn't make sense.首先,Crescent 的“警告:”段落中的推理没有意义。 The explanation implies that coding "a bunch of if (error instanceof MyError) else ..." is somehow burdensome or verbose compared to multiple catch statements.该解释暗示,与多个 catch 语句相比,编码“一堆 if (error instanceof MyError) else ...”在某种程度上是繁琐或冗长的。 Multiple instanceof statements in a single catch block are just as concise as multiple catch statements-- clean and concise code without any tricks.单个 catch 块中的多个 instanceof 语句与多个 catch 语句一样简洁——干净简洁的代码没有任何技巧。 This is a great way to emulate Java's great throwable-subtype-specific error handling.这是模拟 Java 出色的 throwable-subtype-specific 错误处理的好方法。

WRT "appears the message property of the subclass does not get set", that is not the case if you use a properly constructed Error subclass. WRT“似乎没有设置子类的消息属性”,如果您使用正确构造的错误子类,则情况并非如此。 To make your own ErrorX Error subclass, just copy the code block beginning with "var MyError =", changing the one word "MyError" to "ErrorX".要创建您自己的 ErrorX Error 子类,只需复制以“var MyError =”开头的代码块,将单词“MyError”更改为“ErrorX”。 (If you want to add custom methods to your subclass, follow the sample text). (如果要向子类添加自定义方法,请按照示例文本进行操作)。

The real and significant limitation of JavaScript error subclassing is that for JavaScript implementations or debuggers that track and report on stack trace and location-of-instantiation, like FireFox, a location in your own Error subclass implementation will be recorded as the instantiation point of the class, whereas if you used a direct Error, it would be the location where you ran "new Error(...)"). JavaScript 错误子类化的真正且重要的限制是,对于跟踪和报告堆栈跟踪和实例化位置的 JavaScript 实现或调试器,如 FireFox,您自己的 Error 子类实现中的一个位置将被记录为实例化点类,而如果您使用直接错误,它将是您运行“new Error(...)”的位置)。 IE users would probably never notice, but users of Fire Bug on FF will see useless file name and line number values reported alongside these Errors, and will have to drill down on the stack trace to element #1 to find the real instantiation location. IE 用户可能永远不会注意到,但 FF 上的 Fire Bug 用户将看到与这些错误一起报告的无用文件名和行号值,并且必须深入到元素 #1 的堆栈跟踪以找到真正的实例化位置。

For the sake of completeness -- just because none of the previous answers mentioned this method -- if you are working with Node.js and don't have to care about browser compatibility, the desired effect is pretty easy to achieve with the built in inherits of the util module ( official docs here ).为了完整起见——仅仅因为之前的答案都没有提到这个方法——如果你正在使用 Node.js 并且不必关心浏览器兼容性,那么使用内置的util模块的inherits此处为官方文档)。

For example, let's suppose you want to create a custom error class that takes an error code as the first argument and the error message as the second argument:例如,假设您要创建一个自定义错误类,该类将错误代码作为第一个参数,将错误消息作为第二个参数:

file custom-error.js :文件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;

Now you can instantiate and pass/throw your 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!');

Note that, with this snippet, the stack trace will have the correct file name and line, and the error instance will have the correct name!请注意,使用此代码段,堆栈跟踪将具有正确的文件名和行,并且错误实例将具有正确的名称!

This happens due to the usage of the captureStackTrace method, which creates a stack property on the target object (in this case, the CustomError being instantiated).这是由于使用了captureStackTrace方法,该方法在目标对象上创建了一个stack属性(在本例中, CustomError被实例化)。 For more details about how it works, check the documentation here .有关其工作原理的更多详细信息,请查看此处的文档。

As some people have said, it's fairly easy with ES6:正如一些人所说,使用 ES6 相当容易:

class CustomError extends Error { }

So I tried that within my app, (Angular, Typescript) and it just didn't work.所以我在我的应用程序(Angular,Typescript)中尝试了它,但它不起作用。 After some time I've found that the problem is coming from Typescript :O一段时间后,我发现问题来自 Typescript :O

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

It's very disturbing because if you do:这非常令人不安,因为如果你这样做:

class CustomError extends Error {}
​

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

In node or directly into your browser it'll display: Custom error在节点或直接进入您的浏览器,它将显示: Custom error

Try to run that with Typescript in your project on on Typescript playground, it'll display Basic error ...尝试在 Typescript 操场上的项目中使用 Typescript 运行它,它会显示Basic error ...

The solution is to do the following:解决方案是执行以下操作:

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

How about this solution?这个解决方案怎么样?

Instead of throwing your custom Error using:而不是使用以下方法抛出您的自定义错误:

throw new MyError("Oops!");

You would wrap the Error object (kind of like a Decorator):您将包装 Error 对象(有点像装饰器):

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

This makes sure all of the attributes are correct, such as the stack, fileName lineNumber, et cetera.这确保所有属性都是正确的,例如堆栈、文件名、行号等。

All you have to do then is either copy over the attributes, or define getters for them.然后你所要做的就是复制这些属性,或者为它们定义 getter。 Here is an example using getters (IE9):这是一个使用 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();
};

My solution is more simple than the other answers provided and doesn't have the downsides.我的解决方案比提供的其他答案更简单,并且没有缺点。

It preserves the Error prototype chain and all properties on Error without needing specific knowledge of them.它保留了 Error 原型链和 Error 上的所有属性,而无需具体了解它们。 It's been tested in Chrome, Firefox, Node, and IE11.它已经在 Chrome、Firefox、Node 和 IE11 中进行了测试。

The only limitation is an extra entry at the top of the call stack.唯一的限制是调用堆栈顶部的额外条目。 But that is easily ignored.但这很容易被忽视。

Here's an example with two custom parameters:这是一个带有两个自定义参数的示例:

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

Example Usage:示例用法:

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
}

For environments that require a polyfil of setPrototypeOf:对于需要 setPrototypeOf 的 polyfil 的环境:

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

In the above example Error.apply (also Error.call ) doesn't do anything for me (Firefox 3.6/Chrome 5).在上面的例子中Error.apply (也是Error.call )对我没有任何作用(Firefox 3.6/Chrome 5)。 A workaround I use is:我使用的解决方法是:

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

Update 2021 2021 年更新

On addition to standard message property, JavaScript now supports adding specific cause of the error as a optional param to the Error constructor:除了标准message属性之外,JavaScript 现在支持将错误的特定cause作为可选参数添加到Error构造函数中:

const error1 = new Error('Error one');
const error2 = new Error('Error two', { cause: error1 });
// error2.cause === error1

In Node as others have said, it's simple:正如其他人所说,在 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);
}

I just want to add to what others have already stated:我只想补充其他人已经说过的内容:

To make sure that the custom error class shows up properly in the stack trace, you need to set the custom error class's prototype's name property to the custom error class's name property.为了确保自定义错误类在堆栈跟踪中正确显示,您需要将自定义错误类的原型名称属性设置为自定义错误类的名称属性。 This is what I mean:这就是我的意思:

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

So the full example would be:所以完整的例子是:

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

When all is said and done, you throw your new exception and it looks like this (I lazily tried this in the chrome dev tools):当一切都完成后,你抛出你的新异常,它看起来像这样(我懒洋洋地在 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)

My 2 cents:我的 2 美分:

Why another answer?为什么是另一个答案?

a) Because accessing the Error.stack property (as in some answers) have a large performance penalty. a) 因为访问Error.stack属性(如在某些答案中)会造成很大的性能损失。

b) Because it is only one line. b) 因为它只有一行。

c) Because the solution at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error does not seems to preserve stack info. 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);
};

usage example使用示例

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

What does it do?它有什么作用?

this.__proto__.__proto__ is MyError.prototype.__proto__ , so it is setting the __proto__ FOR ALL INSTANCES of MyError to a specific newly created Error. this.__proto__.__proto__MyError.prototype.__proto__ ,因此它将MyError.prototype.__proto____proto__ FOR ALL INSTANCES 设置为特定的新创建的错误。 It keeps MyError class properties and methods and also puts the new Error properties (including .stack) in the __proto__ chain.它保留 MyError 类属性和方法,并将新的 Error 属性(包括 .stack)放入__proto__链中。

Obvious problem:明显的问题:

You can not have more than one instance of MyError with useful stack info.您不能拥有多个具有有用堆栈信息的 MyError 实例。

Do not use this solution if you do not fully understand what this.__proto__.__proto__= does.如果您不完全了解this.__proto__.__proto__=作用,请不要使用此解决方案。

Since JavaScript Exceptions are difficult to sub-class, I don't sub-class.由于 JavaScript 异常很难进行子类化,因此我不进行子类化。 I just create a new Exception class and use an Error inside of it.我只是创建了一个新的 Exception 类并在其中使用了一个 Error 。 I change the Error.name property so that it looks like my custom exception on the console:我更改了 Error.name 属性,使其看起来像我在控制台上的自定义异常:

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

The above new exception can be thrown just like a regular Error and it will work as expected, for example:上面的新异常可以像常规错误一样抛出,它会按预期工作,例如:

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

Caveat: the stack trace is not perfect, as it will bring you to where the new Error is created and not where you throw.警告:堆栈跟踪并不完美,因为它会将您带到创建新错误的位置,而不是您抛出的位置。 This is not a big deal on Chrome because it provides you with a full stack trace directly in the console.这在 Chrome 上没什么大不了的,因为它直接在控制台中为您提供了完整的堆栈跟踪。 But it's more problematic on Firefox, for example.但是,例如,在 Firefox 上问题更多。

As pointed out in Mohsen's answer, in ES6 it's possible to extend errors using classes.正如 Mohsen 的回答中所指出的,在 ES6 中,可以使用类来扩展错误。 It's a lot easier and their behavior is more consistent with native errors...but unfortunately it's not a simple matter to use this in the browser if you need to support pre-ES6 browsers.这要容易得多,而且它们的行为更符合本机错误……但不幸的是,如果您需要支持 ES6 之前的浏览器,在浏览器中使用它并不是一件简单的事情。 See below for some notes on how that might be implemented, but in the meantime I suggest a relatively simple approach that incorporates some of the best suggestions from other answers:有关如何实施的一些说明,请参见下文,但与此同时,我建议采用一种相对简单的方法,该方法结合了其他答案中的一些最佳建议:

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

In ES6 it's as simple as:在 ES6 中,它很简单:

class CustomError extends Error {}

...and you can detect support for ES6 classes with try {eval('class X{}') , but you'll get a syntax error if you attempt to include the ES6 version in a script that's loaded by older browsers. ...并且您可以使用try {eval('class X{}')检测对 ES6 类的支持,但是如果您尝试在旧浏览器加载的脚本中包含 ES6 版本,则会出现语法错误。 So the only way to support all browsers would be to load a separate script dynamically (eg via AJAX or eval() ) for browsers that support ES6.因此,支持所有浏览器的唯一方法是为支持 ES6 的浏览器动态加载单独的脚本(例如,通过 AJAX 或eval() )。 A further complication is that eval() isn't supported in all environments (due to Content Security Policies), which may or may not be a consideration for your project.更复杂的是,并非所有环境都支持eval() (由于内容安全策略),这可能是您的项目的考虑因素,也可能不是。

So for now, either the first approach above or simply using Error directly without trying to extend it seems to be the best that can practically be done for code that needs to support non-ES6 browsers.所以就目前而言,无论是上面的第一种方法还是直接使用Error而不尝试扩展它似乎是对于需要支持非 ES6 浏览器的代码实际上可以做到的最好的方法。

There is one other approach that some people might want to consider, which is to use Object.setPrototypeOf() where available to create an error object that's an instance of your custom error type but which looks and behaves more like a native error in the console (thanks to Ben's answer for the recommendation).有些人可能想要考虑另一种方法,即使用Object.setPrototypeOf()来创建一个错误对象,该对象是自定义错误类型的实例,但其外观和行为更像是控制台中的本机错误(感谢Ben对推荐的回答)。 Here's my take on that approach: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 .这是我对这种方法的看法: https : //gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 But given that one day we'll be able to just use ES6, personally I'm not sure the complexity of that approach is worth it.但考虑到有一天我们将能够只使用 ES6,我个人不确定这种方法的复杂性是否值得。

Mohsen has a great answer above in ES6 that sets the name, but if you're using TypeScript or if you're living in the future where hopefully this proposal for public and private class fields has moved past stage 3 as a proposal and made it into stage 4 as part of ECMAScript/JavaScript then you might want to know this is then just a little bit shorter. Mohsen 在 ES6 上面有一个很好的答案来设置名称,但是如果你正在使用 TypeScript 或者如果你生活在未来,希望这个关于公共和私有类字段的提案已经作为提案通过了第 3 阶段并成功了作为 ECMAScript/JavaScript 的一部分进入第 4 阶段,那么您可能想知道这会更短一些。 Stage 3 is where browsers start implementing features, so if your browser supports it the code below just might work.第 3 阶段是浏览器开始实现功能的地方,因此如果您的浏览器支持它,下面的代码可能会起作用。 (Tested in the new Edge browser v81 it seems to work fine). (在新的 Edge 浏览器 v81 中测试它似乎工作正常)。 Be warned though this is an unstable feature at the moment and should be used cautiously and you should always check browser support on unstable features.请注意,尽管目前这是一个不稳定的功能,应该谨慎使用,并且您应该始终检查浏览器对不稳定功能的支持。 This post is mainly for those future dwellers when browsers might support this.这篇文章主要针对那些浏览器可能支持此功能的未来居民。 To check support check MDN and Can I use .要检查支持检查MDN我可以使用. It's currently got 66% support across the browser market which is getting there but not that great so if you really want to use it now and don't want to wait either use a transpiler like Babel or something like TypeScript .它目前在浏览器市场上获得了 66% 的支持,但不是那么好,所以如果你现在真的想使用它并且不想等待,要么使用像Babel这样的转译器或像TypeScript这样的东西。

 class EOFError extends Error { name="EOFError" } throw new EOFError("Oops errored");

Compare this to a nameless error which when thrown will not log it's name.将此与无名错误进行比较,该错误在抛出时不会记录其名称。

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

The way to do this right is to return the result of the apply from the constructor, as well as setting the prototype in the usual complicated javascripty way:正确执行此操作的方法是从构造函数返回 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 ...>

The only problems with this way of doing it at this point (i've iterated it a bit) are that在这一点上这样做的唯一问题(我已经迭代了一点)是

  • properties other than stack and message aren't included in MyError and除了stackmessage之外的属性不包含在MyError
  • the stacktrace has an additional line that isn't really necessary.堆栈跟踪有一个额外的行,这并不是真正必要的。

The first problem could be fixed by iterating through all the non-enumerable properties of error using the trick in this answer: Is it possible to get the non-enumerable inherited property names of an object?第一个问题可以通过使用此答案中的技巧迭代所有不可枚举的错误属性来解决: Is it possible to get the non-enumerable继承的属性名称对象? , but this isn't supported by ie<9. ,但这不受 ie<9 支持。 The second problem could be solved by tearing out that line in the stack trace, but I'm not sure how to safely do that (maybe just removing the second line of e.stack.toString() ??).第二个问题可以通过删除堆栈跟踪中的那一行来解决,但我不确定如何安全地做到这一点(也许只是删除 e.stack.toString() 的第二行??)。

The snippet shows it all.片段显示了这一切。

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

I didn't like all the other answers, too long, too complicated or didn't trace the stack correctly.我不喜欢所有其他答案,太长、太复杂或没有正确跟踪堆栈。 Here my approach, if you need more custom props pass them to the constructor and set them like name.这是我的方法,如果您需要更多自定义道具,请将它们传递给构造函数并将它们设置为名称。

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

My proposed solution is to use the .name property of error to distinguish between error types instead of instancof我提出的解决方案是使用 error 的.name属性来区分错误类型而不是instancof

This doesn't exactly answer the question, but I think makes for a reasonable solution, for some scenarios anyway.这并不能完全回答这个问题,但我认为无论如何对于某些情况,这是一个合理的解决方案。

The benefit I've seen of being able to have an instanceof CustomError is that you can do custom handling in your promise catch handler.我已经看到能够拥有instanceof CustomError的好处是您可以在您的承诺捕获处理程序中进行自定义处理。

For example:例如:

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

If that's what you're trying to accomplish, you be be well suited by the .name parameter as-well though:如果这就是您要完成的任务,那么您也很适合.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
  })

I would take a step back and consider why you want to do that?我会退后一步考虑你为什么要这样做? I think the point is to deal with different errors differently.我认为关键是要以不同的方式处理不同的错误。

For example, in Python, you can restrict the catch statement to only catch MyValidationError , and perhaps you want to be able to do something similar in javascript.例如,在 Python 中,您可以将 catch 语句限制为仅捕获MyValidationError ,也许您希望能够在 javascript 中执行类似的操作。

catch (MyValidationError e) {
    ....
}

You can't do this in javascript.你不能在 javascript 中做到这一点。 There's only going to be one catch block.只会有一个 catch 块。 You're supposed to use an if statement on the error to determine its type.您应该对错误使用 if 语句来确定其类型。

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

I think I would instead throw a raw object with a type, message, and any other properties you see fit.我想我会抛出一个带有类型、消息和任何其他你认为合适的属性的原始对象。

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

And when you catch the error:当您发现错误时:

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

Custom Error Decorator自定义错误装饰器

This is based on George Bailey's answer , but extends and simplifies the original idea.这是基于George Bailey 的回答,但扩展并简化了最初的想法。 It is written in CoffeeScript, but is easy to convert to JavaScript.它是用 CoffeeScript 编写的,但很容易转换为 JavaScript。 The idea is extend Bailey's custom error with a decorator that wraps it, allowing you to create new custom errors easily.这个想法是使用包装它的装饰器扩展 Bailey 的自定义错误,让您轻松创建新的自定义错误。

Note: This will only work in V8.注意:这仅适用于 V8。 There is no support for Error.captureStackTrace in other environments.在其他环境中不支持Error.captureStackTrace

Define定义

The decorator takes a name for the error type, and returns a function that takes an error message, and encloses the error name.装饰器为错误类型取一个名称,并返回一个接收错误消息并包含错误名称的函数。

CoreError = (@message) ->

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

BaseError = (type) ->

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

Use采用

Now it is simple to create new error types.现在很容易创建新的错误类型。

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

For fun, you could now define a function that throws a SignatureError if it is called with too many args.为了好玩,你现在可以定义一个函数,如果它被调用的参数太多,就会抛出一个SignatureError

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

This has been tested pretty well and seems to work perfectly on V8, maintaing the traceback, position etc.这已经过很好的测试,似乎在 V8 上工作得很好,维护了回溯、位置等。

Note: Using new is optional when constructing a custom error.注意:在构建自定义错误时使用new是可选的。

if you don't care about performances for errors this is the smallest you can do如果你不关心错误的表现,这是你能做的最小的事情

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

you can use it without new just MyError(message)您可以在没有新的 MyError(message) 的情况下使用它

By changing the prototype after the constructor Error is called we don't have to set the callstack and message通过在构造函数 Error 被调用后更改原型,我们不必设置调用堆栈和消息

This isn't that complicated, but I personally find it the easiest way to extend an error easily.这并不复杂,但我个人认为这是轻松扩展错误的最简单方法。

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

Create a utility class like so called ExtendableError .创建一个实用程序类,如所谓的ExtendableError The purpose of this utility class is to be like the normal Error class, but change the name property to the name of the class by default, so it's really easy to extend an error.这个实用类的目的是和普通的Error类一样,但是默认把name属性改成类的name ,这样扩展错误真的很容易。

Now, if you want to extend an error, it only takes one line.现在,如果你想扩展一个错误,它只需要一行。

class MyError extends ExtendableError {}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM