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

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

在JS中应该做什么?


当前回答

自定义错误装饰器

这是基于George Bailey的回答,但扩展并简化了最初的想法。它是用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 }"

Use

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

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

为了好玩,现在可以定义一个函数,如果调用时带有太多参数,则抛出SignatureError。

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

这已经被测试得很好,似乎在V8上工作得很好,保持回溯,位置等。

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

其他回答

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

在ES6:

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

这段代码显示了这一切。

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

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

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

在节点v16.9.0中提供。 适用于Chrome, Firefox和Safari(见浏览器兼容性版本)

简而言之:

如果你使用的是不带转译器的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