自定义异常类型

我可以在JavaScript中为用户定义的异常定义自定义类型吗?如果是,我该怎么做?

121464 次浏览

使用语句。

JavaScript不关心异常类型是什么(就像Java一样)。 JavaScript只是注意到,有一个异常,当你捕捉它时,你可以“看看”异常“说”什么

如果你有不同的异常类型,你必须抛出,我建议使用变量包含异常的字符串/对象,即消息。在需要它的地方使用“throw myException”,在catch中,将捕获的异常与myException进行比较。

是的。你可以抛出任何你想抛出的东西:整数、字符串、对象等等。如果要抛出一个对象,那么只需创建一个新对象,就像在其他情况下创建一个对象一样,然后抛出它。Mozilla的Javascript参考有几个例子。

你可以实现你自己的异常和它们的处理,例如:

// define exceptions "classes"
function NotNumberException() {}
function NotPositiveNumberException() {}


// try some code
try {
// some function/code that can throw
if (isNaN(value))
throw new NotNumberException();
else
if (value < 0)
throw new NotPositiveNumberException();
}
catch (e) {
if (e instanceof NotNumberException) {
alert("not a number");
}
else
if (e instanceof NotPositiveNumberException) {
alert("not a positive number");
}
}

还有另一种捕获类型化异常的语法,尽管这并不适用于所有浏览器(例如不是在IE中):

// define exceptions "classes"
function NotNumberException() {}
function NotPositiveNumberException() {}


// try some code
try {
// some function/code that can throw
if (isNaN(value))
throw new NotNumberException();
else
if (value < 0)
throw new NotPositiveNumberException();
}
catch (e if e instanceof NotNumberException) {
alert("not a number");
}
catch (e if e instanceof NotPositiveNumberException) {
alert("not a positive number");
}

WebReference:

throw {
name:        "System Error",
level:       "Show Stopper",
message:     "Error detected. Please contact the system administrator.",
htmlMessage: "Error detected. Please contact the <a href=\"mailto:sysadmin@acme-widgets.com\">system administrator</a>.",
toString:    function(){return this.name + ": " + this.message;}
};
function MyError(message) {
this.message = message;
}


MyError.prototype = new Error;

这允许使用像..

try {
something();
} catch(e) {
if(e instanceof MyError)
doSomethingElse();
else if(e instanceof Error)
andNowForSomethingCompletelyDifferent();
}

下面是如何创建与本机Error行为完全相同的自定义错误。这个技术现在仅适用于Chrome和node.js。我还不建议使用,如果你不明白它是做什么的。

Error.createCustromConstructor = (function() {


function define(obj, prop, value) {
Object.defineProperty(obj, prop, {
value: value,
configurable: true,
enumerable: false,
writable: true
});
}


return function(name, init, proto) {
var CustomError;
proto = proto || {};
function build(message) {
var self = this instanceof CustomError
? this
: Object.create(CustomError.prototype);
Error.apply(self, arguments);
Error.captureStackTrace(self, CustomError);
if (message != undefined) {
define(self, 'message', String(message));
}
define(self, 'arguments', undefined);
define(self, 'type', undefined);
if (typeof init == 'function') {
init.apply(self, arguments);
}
return self;
}
eval('CustomError = function ' + name + '() {' +
'return build.apply(this, arguments); }');
CustomError.prototype = Object.create(Error.prototype);
define(CustomError.prototype, 'constructor', CustomError);
for (var key in proto) {
define(CustomError.prototype, key, proto[key]);
}
Object.defineProperty(CustomError.prototype, 'name', { value: name });
return CustomError;
}


})();

结果我们得到

/**
* name   The name of the constructor name
* init   User-defined initialization function
* proto  It's enumerable members will be added to
*        prototype of created constructor
**/
Error.createCustromConstructor = function(name, init, proto)

然后你可以这样使用它:

var NotImplementedError = Error.createCustromConstructor('NotImplementedError');

并像使用Error一样使用NotImplementedError:

throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');

它的行为是预期的:

err instanceof NotImplementedError               // -> true
err instanceof Error                             // -> true
NotImplementedError.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> NotImplementedError
err.name                                         // -> NotImplementedError
err.message                                      // -> Not yet...
err.toString()                                   // -> NotImplementedError: Not yet...
err.stack                                        // -> works fine!

注意,error.stack工作完全正确,并且不会包含NotImplementedError构造函数调用(感谢v8的Error.captureStackTrace())。

请注意。有一个丑陋的eval()。使用它的唯一原因是得到正确的err.constructor.name。如果你不需要它,你可以简化一切。

我经常使用带有原型继承的方法。重写toString()给你带来的好处是,像Firebug这样的工具会将实际信息而不是[object Object]记录到控制台,以查找未捕获的异常。

使用instanceof来确定异常的类型。

main.js

// just an exemplary namespace
var ns = ns || {};


// include JavaScript of the following
// source files here (e.g. by concatenation)


var someId = 42;
throw new ns.DuplicateIdException('Another item with ID ' +
someId + ' has been created');
// Firebug console:
// uncaught exception: [Duplicate ID] Another item with ID 42 has been created

Exception.js

ns.Exception = function() {
}


/**
* Form a string of relevant information.
*
* When providing this method, tools like Firebug show the returned
* string instead of [object Object] for uncaught exceptions.
*
* @return {String} information about the exception
*/
ns.Exception.prototype.toString = function() {
var name = this.name || 'unknown';
var message = this.message || 'no description';
return '[' + name + '] ' + message;
};

DuplicateIdException.js

ns.DuplicateIdException = function(message) {
this.name = 'Duplicate ID';
this.message = message;
};


ns.DuplicateIdException.prototype = new ns.Exception();
//create error object
var error = new Object();
error.reason="some reason!";


//business function
function exception(){
try{
throw error;
}catch(err){
err.reason;
}
}

现在我们设置,添加原因或任何我们想要的属性到错误对象并检索它。通过使误差更合理。

你应该创建一个扩展Error的自定义异常:

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

注意:需要设置this.name,以便.toString().stack打印InvalidArgumentException而不是Error

对于旧的Javascript环境,你可以做原型继承:

function InvalidArgumentException(message) {
this.message = message;
// Use V8's native method if available, otherwise fallback
if ("captureStackTrace" in Error)
Error.captureStackTrace(this, InvalidArgumentException);
else
this.stack = (new Error()).stack;
}


InvalidArgumentException.prototype = Object.create(Error.prototype);
InvalidArgumentException.prototype.name = "InvalidArgumentException";
InvalidArgumentException.prototype.constructor = InvalidArgumentException;

这基本上是disfated发布的简化版本,增强了堆栈跟踪在Firefox和其他浏览器上的工作。

两者都满足所有这些测试:

用法:

throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");

它的行为是预期的:

err instanceof InvalidArgumentException          // -> true
err instanceof Error                             // -> true
InvalidArgumentException.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err)               // -> true
err.constructor.name                             // -> InvalidArgumentException
err.name                                         // -> InvalidArgumentException
err.message                                      // -> Not yet...
err.toString()                                   // -> InvalidArgumentException: Not yet...
err.stack                                        // -> works fine!

参见MDN中的这个例子

如果你需要定义多个错误(测试代码在这里!):

function createErrorType(name, initFunction) {
function E(message) {
this.message = message;
if (Error.captureStackTrace)
Error.captureStackTrace(this, this.constructor);
else
this.stack = (new Error()).stack;
initFunction && initFunction.apply(this, arguments);
}
E.prototype = Object.create(Error.prototype);
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
var InvalidStateError = createErrorType(
'InvalidStateError',
function (invalidState, acceptedStates) {
this.message = 'The state ' + invalidState + ' is invalid. Expected ' + acceptedStates + '.';
});


var error = new InvalidStateError('foo', 'bar or baz');
function assert(condition) { if (!condition) throw new Error(); }
assert(error.message);
assert(error instanceof InvalidStateError);
assert(error instanceof Error);
assert(error.name == 'InvalidStateError');
assert(error.stack);
error.message;

代码主要从:什么's一个很好的方法来扩展错误在JavaScript?复制

用于ES2015类的艾瑟琳说答案的替代方案

class InvalidArgumentException extends Error {
constructor(message) {
super();
Error.captureStackTrace(this, this.constructor);
this.name = "InvalidArgumentException";
this.message = message;
}
}

简而言之:

  • 如果你正在使用ES6 没有transpilers:

    class CustomError extends Error { /* ... */}
    

    参见在ES6语法中Javascript扩展错误了解当前的最佳实践

  • 如果你正在使用Babel transpiler:

选项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) {
    const 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

Explanation:

Why extending the Error class using ES6 and Babel is a problem?

Because an instance of CustomError is not anymore recognized as such.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

事实上,从Babel的官方文档中,你不能扩展任何内置JavaScript类,如DateArrayDOMError

问题描述如下:

那么其他的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

ES6

使用新的类和扩展关键字,现在更容易:

class CustomError extends Error {
constructor(message) {
super(message);
//something
}
}