【发布时间】:2010-11-25 19:25:54
【问题描述】:
我想在我的 JS 代码中抛出一些东西,我希望它们是 instanceof Error,但我也想让它们成为其他东西。
在 Python 中,通常会继承 Exception。
在 JS 中应该做什么?
【问题讨论】:
标签: javascript exception error-handling
我想在我的 JS 代码中抛出一些东西,我希望它们是 instanceof Error,但我也想让它们成为其他东西。
在 Python 中,通常会继承 Exception。
在 JS 中应该做什么?
【问题讨论】:
标签: javascript exception error-handling
在 ES6 中:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
【讨论】:
this.name = this.constructor.name;。
错误对象的唯一标准字段是message 属性。 (请参阅 MDN 或 EcmaScript 语言规范,第 15.11 节)其他一切都是特定于平台的。
大多数环境设置了stack 属性,但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
您可以嗅探堆栈,从中移除不需要的元素并提取文件名和行号等信息,但这样做需要有关 JavaScript 当前运行平台的信息。大多数情况下是不必要的——如果你真的愿意,你可以在事后进行。
Safari 是一个明显的例外。没有stack 属性,但是throw 关键字设置了被抛出对象的sourceURL 和line 属性。这些东西保证是正确的。
我使用的测试用例可以在这里找到:JavaScript self-made Error object comparison。
【讨论】:
this.name = 'MyError'移出函数并将其更改为MyError.prototype.name = 'MyError'。
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError。
this吧?
简而言之:
如果你使用的是 ES6没有转译器:
class CustomError extends Error { /* ... */}
如果您使用的是 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);
如果您使用的是纯 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;
}
替代方案:使用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
其实从Babel的官方文档来看,你cannot extend any built-in JavaScript classes比如Date、Array、DOM或者Error。
这里描述了这个问题:
其他 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 问题,还可以保留常规错误console.log:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
【讨论】:
class CustomError extends Error { /* ... */} 不能正确处理特定于供应商的参数(lineNumber 等),“使用 ES6 语法扩展 Javascript 中的错误”是 Babel 特定的,您的 ES5 解决方案使用 const 并且它不处理自定义参数。
console.log(new CustomError('test') instanceof CustomError);// false 的问题在撰写本文时是正确的,但现在已经解决。事实上the issue linked in the answer 已经解决了,我们可以测试here 的正确行为,并将代码粘贴到REPL 中,看看它是如何正确转译以使用正确的原型链进行实例化的。
编辑:请阅读 cmets。事实证明这只适用于 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
}
Original post on "Show me the code !"
短版:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
我将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)
当你继承Error 时,toString 将不复存在。所以你也需要添加。
else
this.toString = function () { return this.name + ': ' + this.message }
IE 不会将UserError 视为instanceof Error,除非您在throw UserError 之前运行以下一段时间
UserError.prototype = Error.prototype
【讨论】:
Error.call(this) 确实没有做任何事情,因为它返回错误而不是修改this。
UserError.prototype = Error.prototype 具有误导性。这不做继承,这使它们同一个类。
Object.setPrototypeOf(this.constructor.prototype, Error.prototype) 比this.constructor.prototype.__proto__ = Error.prototype 更受欢迎,至少对于当前的浏览器而言。
this.constructor.prototype = Object.create(Error.prototype);?似乎比使用 __proto__ 更干净
为了避免样板文件针对每种不同类型的错误,我将一些解决方案的智慧结合到了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;
}
然后您可以轻松定义新的错误类型,如下所示:
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';
});
【讨论】:
this.name = name;这一行吗?
name 已经设置在原型上,所以不再需要了。我删除了它。谢谢!
在2018,我认为这是最好的方式;支持 IE9+ 和现代浏览器。
更新:请参阅 this test 和 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__ 属性是deprecated,它在其他答案中被广泛使用。
【讨论】:
setPrototypeOf()?至少根据 MDN,如果您可以通过在构造函数上设置 .prototype 属性来完成相同的事情,通常不鼓励使用它(就像您在 else 块中为没有 @ 的浏览所做的那样987654331@).
setPrototypeOf。但是如果你仍然需要它(正如 OP 所要求的那样),你应该使用内置的方法。正如 MDN 所指出的,这被认为是设置对象原型的正确方法。换句话说,MDN 说不要更改原型(因为它会影响性能和优化),但如果必须,请使用 setPrototypeOf。
CustomError.prototype = Object.create(Error.prototype))。此外,Object.setPrototypeOf(CustomError, Error.prototype) 正在设置构造函数本身的原型,而不是为 CustomError 的新实例指定原型。无论如何,在 2016 年我认为实际上有更好的方法来扩展错误,尽管我仍在研究如何将它与 Babel 一起使用:github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
CustomError.prototype = Object.create(Error.prototype) 也在改变原型。您必须更改它,因为 ES5 中没有内置的扩展/继承逻辑。我敢肯定你提到的 babel 插件会做类似的事情。
Object.setPrototypeOf 没有意义,至少在您使用它的方式上没有意义:gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348。也许您打算写Object.setPrototypeOf(CustomError.prototype, Error.prototype) - 这会更有意义(尽管仍然比简单地设置CustomError.prototype 没有任何好处)。
Crescent Fresh 的回答被高度投票的回答具有误导性。尽管他的警告无效,但还有其他一些限制他没有解决。
首先,Crescent 的“警告:”段落中的推理没有意义。这种解释意味着与多个 catch 语句相比,编码“一堆 if (error instanceof MyError) else ...”在某种程度上是繁琐或冗长的。单个 catch 块中的多个 instanceof 语句与多个 catch 语句一样简洁——干净简洁的代码,没有任何技巧。这是模拟 Java 出色的 throwable-subtype-specific 错误处理的好方法。
WRT“似乎没有设置子类的消息属性”,如果您使用正确构造的错误子类,情况并非如此。要创建自己的 ErrorX 错误子类,只需复制以“var MyError =”开头的代码块,将一个单词“MyError”更改为“ErrorX”。 (如果您想向子类添加自定义方法,请按照示例文本进行操作)。
JavaScript 错误子类化的真正重要限制是,对于跟踪和报告堆栈跟踪和实例化位置的 JavaScript 实现或调试器,如 FireFox,您自己的错误子类实现中的位置将被记录为实例化类的点,而如果您使用直接错误,它将是您运行“新错误(...)”的位置)。 IE 用户可能永远不会注意到,但在 FF 上使用 Fire Bug 的用户会看到与这些错误一起报告的无用文件名和行号值,并且必须深入到元素 #1 的堆栈跟踪以找到真正的实例化位置。
【讨论】:
Crescent Fresh's 已被删除!
为了完整起见——只是因为之前的答案都没有提到这种方法——如果你正在使用 Node.js 并且不必关心浏览器的兼容性,那么使用它很容易达到预期的效果util 模块 (official docs here) 的内置 inherits。
例如,假设您要创建一个自定义错误类,该类将错误代码作为第一个参数,将错误消息作为第二个参数:
文件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;
现在您可以实例化并传递/抛出您的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!');
请注意,使用此 sn-p,堆栈跟踪将具有正确的文件名和行,并且错误实例将具有正确的名称!
这是因为使用了captureStackTrace 方法,该方法在目标对象上创建了一个stack 属性(在这种情况下,CustomError 被实例化)。有关其工作原理的更多详细信息,请查看文档here。
【讨论】:
this.message = this.message; 这是错的还是还有我不知道的关于 JS 的疯狂事情?
正如一些人所说,使用 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');
}
}
在节点中或直接在浏览器中显示: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;
}
}
【讨论】:
tsconfig.json中的target设置?如果目标是es5,则需要它,因为否则转译会丢失正确的原型信息。如果目标是es6 和更新的,转译保持class 语法,NodeJS 将按预期处理它而没有任何变通办法?
target: "es5"时重现这个问题。
ES3 和ES5 失败; ES2015 上的任何内容都按预期保留了 CustomError 原型。当然,CustomError 也是 instanceof Error,所以测试应该测试最具体的类。
这个解决方案怎么样?
而不是使用以下方法抛出您的自定义错误:
throw new MyError("Oops!");
你可以包装 Error 对象(有点像装饰器):
throw new MyError(Error("Oops!"));
这可以确保所有属性都是正确的,例如堆栈、文件名 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();
};
【讨论】:
new MyErr (arg1, arg2, new Error()) 在 MyErr 构造函数中,我们使用 Object.assign 将最后一个 arg 的属性分配给 this
我的解决方案比提供的其他答案更简单,并且没有缺点。
它保留了 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}}
);
示例用法:
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
}
对于需要 setPrototypeOf 的 polyfil 的环境:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
【讨论】:
throw CustomError('err') 而不是 throw new CustomError('err')
在上面的例子中,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';
【讨论】:
正如其他人所说,在 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);
}
【讨论】:
我不喜欢所有其他答案,太长、太复杂或没有正确跟踪堆栈。这是我的方法,如果您需要更多自定义道具,请将它们传递给构造函数并将它们设置为名称。
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')
【讨论】:
我只是想补充一下其他人已经说过的内容:
为确保自定义错误类在堆栈跟踪中正确显示,您需要将自定义错误类的原型的名称属性设置为自定义错误类的名称属性。 这就是我的意思:
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)
【讨论】:
我的 2 美分:
a)因为访问 Error.stack 属性(如在某些答案中)会导致很大的性能损失。
b) 因为只有一行。
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);
};
使用示例
http://jsfiddle.net/luciotato/xXyeB/
this.__proto__.__proto__ 是MyError.prototype.__proto__,因此它为所有实例设置__proto__
MyError 到特定的新创建的错误。它保留了 MyError 类的属性和方法,并将新的 Error 属性(包括 .stack)放入 __proto__ 链中。
您不能拥有多个具有有用堆栈信息的 MyError 实例。
如果您不完全了解 this.__proto__.__proto__= 的作用,请勿使用此解决方案。
【讨论】:
除了标准的message 属性之外,JavaScript 现在支持将特定的错误cause 作为可选参数添加到Error 构造函数:
const error1 = new Error('Error one');
const error2 = new Error('Error two', { cause: error1 });
// error2.cause === error1
【讨论】:
由于 JavaScript 异常很难子类化,我不子类化。我只是创建一个新的异常类并在其中使用一个错误。我更改了 Error.name 属性,使其看起来像控制台上的自定义异常:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
上面的新异常可以像常规错误一样抛出,它会按预期工作,例如:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
警告:堆栈跟踪并不完美,因为它会将您带到创建新错误的位置,而不是您抛出的位置。这在 Chrome 上没什么大不了的,因为它直接在控制台中为您提供了完整的堆栈跟踪。但例如在 Firefox 上问题更大。
【讨论】:
m = new InvalidInputError(); dontThrowMeYet(m);的情况下失败
m = new ... 然后Promise.reject(m)。没必要,但代码更容易阅读。
正如 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 {}
...您可以使用try {eval('class X{}') 检测对 ES6 类的支持,但是如果您尝试在旧浏览器加载的脚本中包含 ES6 版本,则会出现语法错误。因此,支持所有浏览器的唯一方法是为支持 ES6 的浏览器动态加载单独的脚本(例如通过 AJAX 或 eval())。更复杂的是,并非所有环境都支持eval()(由于内容安全策略),这可能是您项目的考虑因素,也可能不是。
所以现在,无论是上面的第一种方法,还是直接使用Error 而不尝试扩展它似乎都是对于需要支持非 ES6 浏览器的代码实际上可以做到的最好的方法。
有些人可能会考虑另一种方法,即使用Object.setPrototypeOf() 创建一个错误对象,该对象是您的自定义错误类型的一个实例,但看起来和行为更像是控制台(感谢Ben's answer 的推荐)。这是我对这种方法的看法:https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8。但考虑到有一天我们将能够只使用 ES6,我个人不确定这种方法的复杂性是否值得。
【讨论】:
Mohsen 在上面的 ES6 中有一个很好的答案来设置名称,但是如果你使用 TypeScript 或者你生活在未来,希望这个 proposal for public and private class fields 已经作为提案通过第 3 阶段并进入第 4 阶段作为 ECMAScript/JavaScript 的一部分,那么您可能想知道这只是稍微短一点。第 3 阶段是浏览器开始实现功能的地方,因此如果您的浏览器支持它,下面的代码可能会起作用。 (在新的 Edge 浏览器 v81 中测试它似乎工作正常)。请注意,尽管目前这是一个不稳定的功能,应谨慎使用,您应始终检查浏览器对不稳定功能的支持。这篇文章主要是针对那些浏览器可能支持的未来居民。要检查支持检查 MDN 和 Can I use。它目前在浏览器市场上获得了 66% 的支持,但不是那么好,所以如果你现在真的想使用它并且不想等待,可以使用像 Babel 这样的转译器或像 TypeScript 这样的东西。
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
将此与抛出时不会记录其名称的无名错误进行比较。
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
【讨论】:
这并不复杂,但我个人认为这是轻松扩展错误的最简单方法。
export default class ExtendableError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
创建一个实用程序类,如所谓的ExtendableError。这个实用程序类的目的是和普通的Error类一样,但是把name属性默认改成类名,这样真的很容易扩展报错。
现在,如果你想扩展一个错误,只需要一行。
class MyError extends ExtendableError {}
【讨论】:
做到这一点的方法是从构造函数返回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 ...>
目前这种方式的唯一问题(我已经迭代了一点)是
stack 和 message 以外的属性不包含在 MyError 和 第一个问题可以通过使用此答案中的技巧迭代所有不可枚举的错误属性来解决:Is it possible to get the non-enumerable inherited property names of an object?,但这不受 ie
【讨论】:
sn-p 说明了一切。
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)
}
}
【讨论】:
我提出的解决方案是使用error的.name属性来区分错误类型而不是instancof
这并不能完全回答问题,但我认为对于某些情况来说,这是一个合理的解决方案。
我看到能够拥有instanceof CustomError 的好处是您可以在您的承诺捕获处理程序中进行自定义处理。
例如:
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
})
如果这就是您想要完成的任务,那么.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
})
【讨论】:
我会退后一步,考虑一下您为什么要这样做?我认为关键是要以不同的方式处理不同的错误。
例如,在 Python 中,您可以将 catch 语句限制为仅捕获 MyValidationError,也许您希望能够在 javascript 中做类似的事情。
catch (MyValidationError e) {
....
}
您不能在 javascript 中执行此操作。只会有一个 catch 块。您应该对错误使用 if 语句来确定其类型。
catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}
我想我会改为抛出一个带有类型、消息和您认为合适的任何其他属性的原始对象。
throw { type: "validation", message: "Invalid timestamp" }
当你发现错误时:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
【讨论】:
error.stack,标准工具无法使用它等等。更好的方法是向错误实例添加属性,例如var e = new Error(); e.type = "validation"; ...
这是基于George Bailey's answer,但扩展和简化了最初的想法。它是用 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 }"
现在创建新的错误类型很简单。
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
为了好玩,您现在可以定义一个函数,如果使用太多 args 调用它会抛出 SignatureError。
f = -> throw SignatureError "too many args" if arguments.length
这已经过很好的测试,似乎可以在 V8 上完美运行,维护回溯、位置等。
注意:在构造自定义错误时,使用new 是可选的。
【讨论】:
如果你不关心错误的表现,这是你能做的最小的事情
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
你可以在没有新的 MyError(message) 的情况下使用它
通过在构造函数Error被调用后改变原型,我们不必设置调用栈和消息
【讨论】: