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

在Python中,通常会子类化Exception。

在JS中应该做什么?


当前回答

在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 {}

...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. So the only way to support all browsers would be to load a separate script dynamically (e.g. via AJAX or eval()) for browsers that support ES6. 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.

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

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). Here's my take on that approach: 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.

其他回答

Crescent Fresh的答案是误导性的。尽管他的警告是无效的,但还有其他一些限制他没有提到。

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

WRT“显示子类的消息属性没有得到设置”,如果使用正确构造的Error子类,则不会出现这种情况。要创建自己的ErrorX Error子类,只需复制以"var MyError ="开头的代码块,将"MyError"改为"ErrorX"。(如果您想向子类添加自定义方法,请遵循示例文本)。

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(...)"). 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.

正确的方法是从构造函数返回apply的结果,以及以通常复杂的javascript方式设置原型:

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 ...>

在这一点上,这种方法唯一的问题(我已经重复了一点)是

除了stack和message之外的属性不包含在MyError和 堆栈跟踪有一个额外的行,这并不是真正必要的。

第一个问题可以通过使用以下回答中的技巧遍历所有不可枚举的error属性来解决:是否可能获得对象的不可枚举继承属性名?,但ie<9不支持。第二个问题可以通过在堆栈跟踪中删除这一行来解决,但我不确定如何安全地做到这一点(可能只是删除e.stack.toString()的第二行??)

我想再补充一下大家已经说过的话:

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

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)

简而言之:

如果你使用的是不带转译器的ES6: 类CustomError扩展错误{/*…* /} 如果你正在使用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);

If you are using pure 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

解释:

为什么使用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类,如Date、Array、DOM或Error。

问题描述如下:

本机扩展破坏HTMLELement, Array和其他 类的一个对象,它扩展了基类型如数组,数字,对象,字符串或错误不是这个类的实例

那么其他的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问题,还可以保留常规的error console.log:

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

在上面的例子中。apply (also 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';