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

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

在JS中应该做什么?


当前回答

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

大多数环境都设置了堆栈属性,但是fileName和lineNumber在继承中实际上是无用的。

所以,极简主义的方法是:

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

您可以嗅探堆栈,从堆栈中移除不需要的元素,并提取文件名和lineNumber等信息,但这样做需要有关JavaScript当前运行的平台的信息。大多数情况下,这是不必要的——如果你真的想,你可以在事后进行分析。

Safari是个明显的例外。没有堆栈属性,但是throw关键字设置了被抛出对象的sourceURL和line属性。这些东西肯定是正确的。

我使用的测试用例可以在这里找到:JavaScript自制错误对象比较。

其他回答

这个解决方案怎么样?

而不是抛出你的自定义错误使用:

throw new MyError("Oops!");

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

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

这将确保所有属性都是正确的,例如堆栈、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();
};

就像有些人说的,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');
  }
}

在节点中或直接在浏览器中显示:自定义错误

尝试在你的项目中使用Typescript在Typescript游乐场上运行,它会显示基本错误…

解决方法如下:

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

因为JavaScript异常很难子类化,所以我不子类化。我只是创建了一个新的Exception类,并在其中使用一个Error。我修改了Error.name属性,使它看起来像我在控制台上的自定义异常:

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

上面的new异常可以像普通的Error一样被抛出,它会像预期的那样工作,例如:

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

注意:堆栈跟踪并不是完美的,因为它只会把你带到创建新错误的地方,而不是你抛出错误的地方。这在Chrome上不是什么大问题,因为它直接在控制台中为您提供了完整的堆栈跟踪。但在Firefox上问题就更多了。

在ES6:

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

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

还要注意__proto__属性已弃用,该属性在其他答案中广泛使用。