我想在我的JS代码中扔一些东西,我想让它们成为Error的实例,但我也想让它们成为其他东西。
在Python中,典型的做法是将Exception子类化。
在JS中应该怎么做呢?
Error对象唯一的标准字段是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
你可以嗅探堆栈,从堆栈中移出不需要的元素,并提取fileName和lineNumber等信息,但这样做需要关于JavaScript当前运行的平台的信息。大多数情况下,这是不需要的 -- 如果你真的想的话,可以在事后分析中进行。
Safari是一个明显的例外。没有stack
属性,但throw
关键字设置了被抛出的对象的sourceURL
和line
属性。这些东西被保证是正确的。
我使用的测试案例可以在这里找到。JavaScript自制的错误对象比较。
编辑:请阅读评论。 我的意图是提供一个跨浏览器的解决方案,它可以在所有的浏览器中工作,并在有支持的地方提供堆栈跟踪。
编辑:我做了这个社区维基以允许更多的编辑。
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
}
简版。
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) //如果不是IE,又名 Error.captureStackTrace(this, this.constructor)
else this.toString = function () { return this.name + ': '
UserError.prototype = Error.prototype
简而言之:
class CustomError extends Error { / ... /}
选项1: 使用[babel-plugin-transform-builtin-extend]() 使用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);
function CustomError(message, fileName, lineNumber) { var instance = new Error(message, fileName, lineNumber); Object.setPrototypeOf(instance, Object.getPrototypeOf(this))。 return instance.返回instance.Object.setPrototypeOf(instance, Object.getPrototypeOf(this))。 } CustomError.prototype = Object.create(Error.prototype, { 构造者。 { 值。 错误: 枚举。 假的。 可写。 true, 可配置。 true } }); 如果(Object.setPrototypeOf){ Object.setPrototypeOf(CustomError, Error); } else { CustomError.proto = 错误。 }
解释:
*为什么使用ES6和Babel扩展Error类是个问题?
因为CustomError的实例不再被识别。
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
事实上,从巴别官方文档来看,你不能扩展任何内置的JavaScript类,如Date
、Array
、DOM
或Error
。
这里描述了这个问题。
所有给出的答案都解决了 "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
为了避免每一种不同类型的错误都有模板,我把一些解决方案的智慧结合到一个 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';
});
在2018年,我认为这是最好的方式。 能支持IE9+和现代浏览器。
更新。 参见[本测试][2]和[repo][3],对不同实现进行比较。
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__
属性是[废弃的][1],在其他答案中广泛使用。
[1]:
为了完整起见--只是因为前面的答案都没有提到这个方法--如果你是用Node.js工作,而且不需要关心浏览器的兼容性,那么使用util
模块([官方文档在此][1])的内置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;
现在你可以实例化并传递/抛出你的 "自定义错误"。
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!');
请注意,有了这个片段,堆栈跟踪将有正确的文件名和行,而错误实例将有正确的名称!这是因为使用了captureStackTrace
方法,该方法在目标对象上创建了stack
属性(本例中,CustomError
是实例)。
这是因为使用了 "captureStackTrace "方法,该方法在目标对象上创建了一个 "stack "属性(在本例中,"CustomError "被实例化)。 关于它如何工作的更多细节,请查看文档[这里][2]。
[1]: https://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor [2]: https://nodejs.org/api/errors.html#errors_error_capturestacktrace_targetobject_constructoropt
Crescent Fresh'的回答获得了很高的票数,是一种误导。 虽然他的警告是无效的,但还有其他限制,他没有解决。
首先,Crescent's "Caveats:"段落中的推理没有意义。 该解释暗示编码"一堆if(error instanceof MyError)else ..."与多个catch语句相比,显得有些累赘或啰嗦。 单个catch块中的多个instanceof语句和多个catch语句一样简洁--干净简洁的代码,没有任何技巧。 这是模仿Java'伟大的可抛型-子类型特定错误处理的一个好方法。
WRT "看起来子类的消息属性没有被设置",如果你使用一个正确构造的Error子类,情况就不是这样了。 要制作你自己的ErrorX错误子类,只需复制以"var MyError ="开始的代码块,将一个词"MyError"改为"ErrorX"。 (如果你想在你的子类中添加自定义方法,请按照示例文本进行操作)。
JavaScript错误子类的真正和重要的限制是,对于跟踪和报告堆栈跟踪和实例化位置的JavaScript实现或调试器,如FireFox,你自己的Error子类实现中的一个位置将被记录为该类的实例化点,而如果你直接使用Error,它将是你运行"new Error(..)"的位置。) IE用户可能永远不会注意到,但FF上的Fire Bug用户会在这些Error旁边看到无用的文件名和行号报告,并且必须在堆栈跟踪中深入到元素#1来找到真正的实例化位置。
这个解决方案如何?
与其使用自定义的Error抛出。
throw new MyError("Oops!");
你可以将Error对象包装起来(有点像Decorator)。
throw new MyError(Error("Oops!"));
这将确保所有的属性都是正确的,比如堆栈、fileName行号等。
然后你要做的就是复制这些属性,或者为它们定义getter。 下面是一个使用getters的例子(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();
};
我的解决方案比其他答案更简单,而且没有'的缺点。
它保留了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:的多文件的环境。
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
在上面的例子中,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';
正如一些人所说,使用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');
}
}
在node中或直接进入浏览器,会显示。
自定义错误
。
试着在你的项目中用Typescript在Typescript playground上运行,它会显示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;
}
}
我只想补充一下其他人已经说过的话。
为了确保自定义错误类在堆栈跟踪中正确显示,你需要将自定义错误类'的原型'的名称属性设置为自定义错误类'的名称属性。 我的意思是:
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);
};
用例
this.__proto__.__proto__
是MyError.prototype.__proto__
,所以它为所有的实例设置了__proto__
。
的MyError类放到一个特定的新创建的Error中。
它保留了MyError类的属性和方法,并将新的Error属性(包括.stack)放在__proto__
链中。
你不能拥有一个以上的MyError实例和有用的堆栈信息。
如果你不完全理解this.__proto__.__proto__=
的作用,请不要使用这个解决方案。
在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);
}
由于JavaScript Exceptions很难子类化,所以我不子类化。 我只是创建了一个新的Exception类,并在里面使用一个Error。 我改变了Error.name属性,使它在控制台中看起来像我的自定义异常。
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
上面的新异常可以像普通的Error一样被抛出,并且会像预期的那样工作,例如:{{6027971}}}。
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
注意:堆栈跟踪并不完美,因为它会将你带到新错误创建的地方,而不是你抛出的地方。 这在Chrome浏览器上不是什么大问题,因为它直接在控制台中为你提供了完整的堆栈跟踪。 但在Firefox上就比较麻烦了,比如说。
正如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版本包含在一个由旧版浏览器加载的脚本中,你'会得到一个语法错误。
因此,支持所有浏览器的唯一方法是动态加载一个单独的脚本(例如,通过AJAX或eval())。 通过AJAX或
eval())为支持ES6的浏览器动态加载一个单独的脚本。 另一个复杂的问题是,
eval()`并不是在所有的环境中都支持(由于内容安全政策),这可能是也可能不是你项目的考虑因素。
所以现在,无论是上面的第一种方法,还是干脆直接使用Error
而不尝试扩展它,对于需要支持非ES6浏览器的代码来说,实际上似乎是最好的办法。
还有一种方法,有些人可能要考虑,那就是在可用的地方使用Object.setPrototypeOf()
来创建一个错误对象,这个错误对象是你的自定义错误类型的一个实例,但它的外观和行为更像控制台中的原生错误(感谢Ben'的回答的推荐)。
下面是我对这种方法的看法。
https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8.
但考虑到有一天我们'将能够只使用ES6,我个人不确定这种方法的复杂性是否值得。
正确的做法是,从构造函数中返回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 ...>
目前这种方式唯一的问题是(我'迭代了一下),就是...。
第一个问题可以通过使用这个答案中的技巧迭代所有错误的非数值属性来解决。 https://stackoverflow.com/questions/8024149/is-it-possible-to-get-the-non-enumerable-inherited-property-names-of-an-object,但是ie<9不支持这个。 第二个问题可以通过撕掉堆栈跟踪中的那行来解决,但我不知道如何安全地做到这一点(也许只是删除e.stack.toString()的第二行?)。
这是基于[George Bailey'的答案][1],但扩展和简化了原来的想法。 它是用CoffeeScript编写的,但很容易转换为JavaScript。 这个想法是用一个包装它的装饰器来扩展Bailey'的自定义错误,允许你轻松创建新的自定义错误。
*注意:这只能在V8中使用。
这只适用于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太多,就会抛出一个 "签名错误"。
f = -> throw SignatureError "too many args" if arguments.length
这已经被测试得很好,似乎在V8上工作得很完美,保持了回溯和位置等。
注意:在构建自定义错误时,使用 "new "是可选的。 当构建一个自定义错误时,使用 "new "是可选的。
我会退一步想一想,你为什么要这样做? 我认为重点是要用不同的方式处理不同的错误。
例如,在Python中,你可以限制catch语句只捕捉MyValidationError
,也许你希望能够在javascript中做类似的事情。
catch (MyValidationError e) {
....
}
你不能在javascript中这样做。 只有一个抓取块,你应该在错误上使用if语句来确定其类型。 你应该在错误上使用if语句来确定其类型。
catch(e) {
if(isMyValidationError(e)) {
...
} 否则 {
//也许要重新投掷?
throw e;
}
}
我想我应该抛出一个带有类型、消息和任何其他你认为合适的属性的原始对象。
throw { type: "validation", message: "Invalid timestamp" }
而当你发现错误时。
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}