什么是扩展JavaScript错误的好方法?

我想扔我的JS代码中的一些东西,我希望他们成为instanceof错误,但我也想让他们成为别的东西。

在Python中,通常,子类是Exception。

在JS中做什么是合适的?

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

Mosts环境设置stack属性,但fileNamelineNumber在inheritance中几乎没有用处。

所以,简约的方法是:

 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关键字设置被引发的对象的sourceURLline属性。 那些事情保证是正确的。

我用过的testing用例可以在这里find: JavaScript自制的Error对象比较 。

在ES6中:

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

资源

编辑:请阅读评论。 事实certificate,这只适用于V8(Chrome / Node.JS)我的意图是提供一个跨浏览器的解决scheme,它可以在所有浏览器中工作,并提供堆栈跟踪支持。

编辑:我让这个社区Wiki允许更多的编辑。

针对V8(Chrome / Node.JS)的解决scheme,可以在Firefox中使用,并且可以修改为在IE中正常运行。 (见post末尾)

 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里面的函数,以保持所有的代码在一起。 但是你也可以用UserErrorreplacethis.constructor ,并且允许你将代码移动到函数之外,所以它只被调用一次。

如果你走这条路线,确保你在第一次抛出UserError 之前调用这一行。

该警告不适用该function,因为function是先创build的,不pipe顺序。 因此,您可以将该function移到文件末尾,没有任何问题。

浏览器兼容性

适用于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) 
  • toString在您inheritanceError时不再存在。 所以你也需要添加。

     else this.toString = function () { return this.name + ': ' + this.message } 
  • IE浏览器不会考虑UserError是一个instanceof Errorinstanceof Error除非你throw UserError之前运行下面的一段时间

     UserError.prototype = Error.prototype 

为了避免每种不同types错误的样板 ,我将一些解决scheme的智慧结合到一个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; } 

那么你可以很容易地定义新的错误types如下:

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

新月新鲜的回答高度投票的答案是误导。 虽然他的警告是无效的,但他还没有其他的限制。

首先,在Crescent的“警戒:”段落中的推理是没有意义的。 这个解释意味着,与多个catch语句相比,编写“一堆if(MyError的错误)else …”在某种程度上是繁重或冗长的。 单个catch块中的多个instanceof语句就像多个catch语句一样简洁 – 没有任何技巧的干净简洁的代码。 这是一个很好的方法来模拟Java伟大的throwable-subtype特定的error handling。

WRT“出现子类的消息属性没有得到设置”,如果您使用正确构造的Error子类,情况并非如此。 要创build自己的ErrorX Error子类,只需复制以“var MyError =”开头的代码块,将一个单词“MyError”更改为“ErrorX”。 (如果你想添加自定义方法到你的子类,请按照示例文本)。

JavaScript错误子类的实际和重要的限制是,对于跟踪和报告堆栈跟踪和实例化位置的JavaScript实现或debugging器,像FireFox,在您自己的Error子类实现中的位置将被logging为实例化类,而如果你使用了一个直接的错误,那就是你运行“new Error(…)”的位置)。 IE用户可能永远不会注意到,但FF上的Fire Bug的用户将看到无用的文件名和行号值与这些错误一起报告,并且将不得不在堆栈轨迹上向下钻取到元素#1以find实际的实例化位置。

2017年 ,我认为这是最好的方法; 支持IE9 +和现代浏览器。

 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__属性被弃用 ,这在其他答案中被广泛使用。

注意:请参阅评论和@ MattBrowne的要点中的讨论。

更新:添加constructor分配。 请参阅此testing以比较不同的实现。 testing模块在这里 。

为了完整起见 – 仅仅因为以前的答案都没有提到这个方法 – 如果你正在使用Node.js,并且不需要关心浏览器的兼容性,那么所需的效果是很容易实现的util模块的inherits ( 官方文档在这里 )。

例如,假设您想要创build一个自定义错误类,它将第一个参数的错误代码和第二个参数的错误消息作为参数:

文件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!'); 

请注意,在这个片段中,堆栈跟踪将具有正确的文件名和行,并且错误实例将具有正确的名称!

发生这种情况是由于使用了captureStackTrace方法,该方法在目标对象上创build一个stack属性(在这种情况下,实例化的是CustomError )。 有关它如何工作的更多细节,请查看文档。

这个解决scheme如何?

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

 throw new MyError("Oops!"); 

你会包装错误对象(有点像装饰):

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

这确保所有的属性都是正确的,比如堆栈,文件名lineNumber等等。

你所要做的就是复制属性,或者为它们定义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(); }; 

我的解决scheme比其他答案更简单,没有缺点。

它保留错误原型链和错误的所有属性,而不需要具体的知识。 已经在Chrome,Firefox,Node和IE11中进行了testing。

唯一的限制是在调用堆栈顶部的额外条目。 但这很容易被忽略。

以下是两个自定义参数的示例:

 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 } 

对于需要polyPil 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 { /* ... */} 
  • 如果你使用Babel译员

选项1:使用babel-plugin-transform-b​​uiltin-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; } 
  • 替代scheme:使用疏散框架

说明:

为什么使用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类 ,如DateArrayDOMError

这里描述的问题是:

  • 本机扩展了HTMLELement,Array和其他
  • 该类的类的一个对象,如Array,Number,Object,String或Error等基types扩展不是此类的实例

那么其他的答案呢?

所有给出的答案解决了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 

我只想补充别人已经说过的话:

为了确保自定义错误类在堆栈跟踪中正确显示,您需要将自定义错误类的原型的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'; 

当所有的说法和完成后,你抛出你的新的exception,它看起来像这样(我懒惰地在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中的解决scheme似乎不能保存堆栈信息。;

 //MyError class constructor function MyError(msg){ this.__proto__.__proto__ = Error.apply(null, arguments); }; 

用法示例

http://jsfiddle.net/luciotato/xXyeB/

它有什么作用?

this.__proto__.__proto__MyError.prototype.__proto__ ,所以它将MyError.prototype.__proto____proto__全部实例设置为一个特定的新创build的错误。 它保留了MyError类的属性和方法,并将新的Error属性(包括.stack)放在__proto__链中。

明显的问题:

你不能有多个有用的堆栈信息MyError的实例。

如果您不完全理解this.__proto__.__proto__=作用,请不要使用这个解决scheme。

由于JavaScript Exceptions很难分类,所以我没有分类。 我只是创build一个新的Exception类,并在其中使用一个错误。 我更改Error.name属性,使其看起来像我在控制台上的自定义exception:

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

上面的新exception可以像正常的错误一样引发,并且可以像预期的那样工作,例如:

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

警告:堆栈跟踪不完美,因为它会将您带到创build新错误的位置,而不是您抛出的位置。 这在Chrome上并不是什么大问题,因为它直接在控制台中为您提供完整的堆栈跟踪。 但是,例如,Firefox的问题更多。

要做到这一点的方法是从构造函数返回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 ...> 

这样做的唯一问题(我已经迭代了一点)是这样的

  • 除了stackmessage以外的属性不包含在MyError
  • stacktrace有一个额外的行,这是不是真的有必要。

第一个问题可以通过使用这个答案中的技巧迭代所有的非可枚举的错误属性来解决: 是否有可能获得一个对象的不可枚举的inheritance属性名称? ,但是这不被ie <9支持。 第二个问题可以通过撕掉堆栈跟踪中的那一行来解决,但我不知道如何安全地做到这一点(也许只是删除e.stack.toString()??)的第二行。

我会后退一步,考虑为什么你想这样做? 我认为重点是以不同的方式处理不同的错误。

例如,在Python中,可以将catch语句限制为仅捕获MyValidationError ,也许您希望能够在JavaScript中执行类似的操作。

 catch (MyValidationError e) { .... } 

你不能在JavaScript中做到这一点。 只有一个catch块。 你应该使用错误的if语句来确定它的types。

catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }

我想我会抛出一个types,消息,和你认为合适的任何其他属性的原始对象。

 throw { type: "validation", message: "Invalid timestamp" } 

当你发现错误时:

 catch(e) { if(e.type === "validation") { // handle error } // re-throw, or whatever else } 

自定义错误装饰器

这是基于乔治·贝利的回答 ,但延伸和简化了原来的想法。 它是用CoffeeScript编写的,但很容易转换成JavaScript。 这个想法是扩展贝利的自定义错误与包装它的装饰,使您可以轻松地创build新的自定义错误。

注意:这只会在V8中起作用。 在其他环境中不支持Error.captureStackTrace

确定

装饰器为错误types取一个名称,并返回一个接收错误消息的函数,并包含错误名称。

 CoreError = (@message) -> @constructor.prototype.__proto__ = Error.prototype Error.captureStackTrace @, @constructor @name = @constructor.name BaseError = (type) -> (message) -> new CoreError "#{ type }Error: #{ message }" 

使用

现在创build新的错误types很简单。

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

为了好玩,现在可以定义一个函数,如果调用了太多的参数,就会抛出SignatureError

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

这已经testing得很好,似乎在V8上完美工作,维护追踪,位置等。

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

正如Mohsen的回答所指出的,在ES6中,可以使用类来扩展错误。 这很容易,他们的行为更加符合本地错误…但不幸的是,如果您需要支持ES6之前的浏览器,那么在浏览器中使用它并不是一件简单的事情。 请参阅下面有关如何实现的一些说明,但同时我build议一个相对简单的方法,其中包含一些其他答案的最佳build议:

 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的浏览器dynamic加载一个单独的脚本(例如,通过AJAX或eval() )。 另一个复杂的情况是eval()在所有的环境中都不被支持(由于内容安全策略),这可能是也可能不是你项目的考虑因素。

所以现在,无论是上面的第一种方法,还是直接使用Error而不尝试扩展,似乎是需要支持非ES6浏览器的代码实际上可以完成的最好的方法。

还有一种方法,有些人可能会考虑使用Object.setPrototypeOf()来创build一个错误对象,该对象是您的自定义错误types的一个实例,但其外观和行为更像是控制台中的本机错误(感谢本的build议答案 )。 这是我采取的方法: https : //gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 。 但是总有一天我们可以使用ES6,个人而言,我不确定这种方法的复杂性是否值得。