如何在JavaScript中创建自定义错误?

出于某种原因,在下面的代码段中,构造函数委托似乎不起作用:

function NotImplementedError() {
Error.apply(this, arguments);
}
NotImplementedError.prototype = new Error();


var nie = new NotImplementedError("some message");
console.log("The message is: '"+nie.message+"'")

运行这个函数会得到The message is: ''。有什么想法为什么,或者是否有更好的方法来创建一个新的Error子类?applying到本机Error构造函数是否存在我不知道的问题?

241508 次浏览

构造函数需要类似于工厂方法,并返回您想要的内容。如果你需要额外的方法/属性,你可以在返回之前将它们添加到对象中。

function NotImplementedError(message) { return new Error("Not implemented", message); }


x = new NotImplementedError();

不过我不明白你为什么要这么做。为什么不直接使用new Error...呢?自定义异常在JavaScript(或任何非类型化语言)中并没有真正添加太多内容。

更新代码,将原型分配给Error。Prototype和instanceof以及你的断言工作。

function NotImplementedError(message = "") {
this.name = "NotImplementedError";
this.message = message;
}
NotImplementedError.prototype = Error.prototype;

不过,我会抛出你自己的对象并检查name属性。

throw {name : "NotImplementedError", message : "too lazy to implement"};

根据评论进行编辑

在看了注释并试图记住为什么我要将prototype分配给Error.prototype而不是new Error(),就像Nicholas Zakas在他的文章中所做的那样,我用下面的代码创建了一个jsFiddle:

function NotImplementedError(message = "") {
this.name = "NotImplementedError";
this.message = message;
}
NotImplementedError.prototype = Error.prototype;


function NotImplementedError2(message = "") {
this.message = message;
}
NotImplementedError2.prototype = new Error();


try {
var e = new NotImplementedError("NotImplementedError message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError = " + (ex1 instanceof NotImplementedError));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}


try {
var e = new NotImplementedError2("NotImplementedError2 message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError2 = " + (ex1 instanceof NotImplementedError2));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}

控制台输出如下。

undefined
ex1 instanceof NotImplementedError = true
ex1 instanceof Error = true
ex1.name = NotImplementedError
ex1.message = NotImplementedError message
Error
at window.onload (http://fiddle.jshell.net/MwMEJ/show/:29:34)
ex1 instanceof NotImplementedError2 = true
ex1 instanceof Error = true
ex1.name = Error
ex1.message = NotImplementedError2 message

这证实了“问题”;我遇到的堆栈属性错误是new Error()创建的行号,而不是throw e发生的行号。然而,这可能比NotImplementedError.prototype.name = "NotImplementedError"行影响Error对象的副作用要好。

另外,注意使用NotImplementedError2,当我没有显式地设置.name时,它等于"Error"。然而,正如评论中提到的,因为该版本将prototype设置为new Error(),所以我可以设置NotImplementedError2.prototype.name = "NotImplementedError2"并没有问题。

标准的这一部分可以解释为什么Error.apply调用没有初始化对象:

15.11.1错误构造函数作为函数调用

当Error作为函数而不是构造函数调用时,它会创建和 初始化一个新的Error对象。因此函数调用Error(…)是 等价于对象创建表达式new Error(… 相同参数。< / p >

在这种情况下,Error函数可能确定它没有作为构造函数被调用,因此它返回一个新的Error实例,而不是初始化this对象。

用下面的代码进行测试似乎证明了这就是实际发生的情况:

function NotImplementedError() {
var returned = Error.apply(this, arguments);
console.log("returned.message = '" + returned.message + "'");
console.log("this.message = '" + this.message + "'");
}
NotImplementedError.prototype = new Error();


var nie = new NotImplementedError("some message");

运行此命令时输出如下:

returned.message = 'some message'
this.message = ''

我也遇到过类似的问题。我的错误需要是instanceof ErrorNotImplemented,它还需要在控制台中产生一致的回溯。

我的解决方案:

var NotImplemented = (function() {
var NotImplemented, err;
NotImplemented = (function() {
function NotImplemented(message) {
var err;
err = new Error(message);
err.name = "NotImplemented";
this.message = err.message;
if (err.stack) this.stack = err.stack;
}
return NotImplemented;
})();
err = new Error();
err.name = "NotImplemented";
NotImplemented.prototype = err;


return NotImplemented;
}).call(this);


// TEST:
console.log("instanceof Error: " + (new NotImplemented() instanceof Error));
console.log("instanceof NotImplemented: " + (new NotImplemented() instanceofNotImplemented));
console.log("message: "+(new NotImplemented('I was too busy').message));
throw new NotImplemented("just didn't feel like it");

使用node.js运行的结果:

instanceof Error: true
instanceof NotImplemented: true
message: I was too busy


/private/tmp/t.js:24
throw new NotImplemented("just didn't feel like it");
^
NotImplemented: just didn't feel like it
at Error.NotImplemented (/Users/colin/projects/gems/jax/t.js:6:13)
at Object.<anonymous> (/Users/colin/projects/gems/jax/t.js:24:7)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:487:10)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)

该错误通过了我的所有3个标准,虽然stack属性是非标准的,但在我的情况下,大多数较新的浏览器都支持它是可以接受的。

我只需要实现这样的东西,发现堆栈在我自己的错误实现中丢失了。我要做的是创建一个虚拟错误,并从中检索堆栈:

My.Error = function (message, innerException) {
var err = new Error();
this.stack = err.stack; // IMPORTANT!
this.name = "Error";
this.message = message;
this.innerException = innerException;
}
My.Error.prototype = new Error();
My.Error.prototype.constructor = My.Error;
My.Error.prototype.toString = function (includeStackTrace) {
var msg = this.message;
var e = this.innerException;
while (e) {
msg += " The details are:\n" + e.message;
e = e.innerException;
}
if (includeStackTrace) {
msg += "\n\nStack Trace:\n\n" + this.stack;
}
return msg;
}

尝试为用户定义的错误类型的每个实例创建一个新的原型对象。它允许instanceof检查像往常一样运行,并且在Firefox和V8 (Chome, nodejs)中正确报告类型和消息。

function NotImplementedError(message){
if(NotImplementedError.innercall===undefined){
NotImplementedError.innercall = true;
NotImplementedError.prototype = new Error(message);
NotImplementedError.prototype.name = "NotImplementedError";
NotImplementedError.prototype.constructor = NotImplementedError;


return new NotImplementedError(message);
}
delete NotImplementedError.innercall;
}

注意,一个额外的条目将在正确的堆栈之前。

< p >更简单的方法。你可以让你的对象从Error对象继承。 例子:< / p >
function NotImplementError(message)
{
this.message = message;
Error.call();
Error.call(message);
}

我们所做的是使用函数call()调用Error类的构造函数,因此基本上与在其他面向对象语言中实现类继承是一样的。

以不能使用instanceof为代价,下面的方法保留了原始的堆栈跟踪,并且没有使用任何非标准的技巧。

// the function itself
var fixError = function(err, name) {
err.name = name;
return err;
}


// using the function
try {
throw fixError(new Error('custom error message'), 'CustomError');
} catch (e) {
if (e.name == 'CustomError')
console.log('Wee! Custom Error! Msg:', e.message);
else
throw e; // unhandled. let it propagate upwards the call stack
}

以上所有的答案都很糟糕,真的。即使是107个向上的那个!真正的答案在这里:

从Error对象继承-message属性在哪里?< / >

TL;博士:

a .没有设置message的原因是Error是一个返回一个新的Error对象的函数,并且以任何方式操作this

B.正确的方法是从构造函数返回apply的结果,以及以通常复杂的javascript方式设置原型:

function MyError() {
var temp = Error.apply(this, arguments);
temp.name = this.name = 'MyError';
this.message = temp.message;
if(Object.defineProperty) {
// getter for more optimizy goodness
/*this.stack = */Object.defineProperty(this, 'stack', {
get: function() {
return temp.stack
},
configurable: true // so you can change it if you want
})
} else {
this.stack = temp.stack
}
}
//inherit prototype using ECMAScript 5 (IE 9+)
MyError.prototype = Object.create(Error.prototype, {
constructor: {
value: MyError,
writable: true,
configurable: true
}
});


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 ...>




 

//for EMCAScript 4 or ealier (IE 8 or ealier), inherit prototype this way instead of above code:
/*
var IntermediateInheritor = function() {};
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor();
*/

你可能会使用一些技巧来枚举tmp Error的所有不可枚举属性来设置它们,而不是显式地只设置stackmessage,但ie<9不支持这种技巧

另一种选择,可能不适用于所有环境。至少可以保证它在nodejs 0.8中工作 这种方法使用一种非标准的方式修改内部原型prop

function myError(msg){
var e = new Error(msg);
_this = this;
_this.__proto__.__proto__ = e;
}
function InvalidValueError(value, type) {
this.message = "Expected `" + type.name + "`: " + value;
var error = new Error(this.message);
this.stack = error.stack;
}
InvalidValueError.prototype = new Error();
InvalidValueError.prototype.name = InvalidValueError.name;
InvalidValueError.prototype.constructor = InvalidValueError;

这在Cesium DeveloperError中很好地实现了:

它的简化形式是:

var NotImplementedError = function(message) {
this.name = 'NotImplementedError';
this.message = message;
this.stack = (new Error()).stack;
}


// Later on...


throw new NotImplementedError();

如果你使用Node/Chrome。下面的代码片段将为您提供满足以下要求的扩展。

  • err instanceof Error
  • err instanceof CustomErrorType
  • console.log()在与消息一起创建时返回[CustomErrorType]
  • console.log()在创建时不带消息返回[CustomErrorType: message]
  • Throw /stack提供错误创建时的信息。
  • 在Node.JS和Chrome中工作最佳。
  • 将在Chrome, Safari, Firefox和IE 8+中通过instanceof检查,但在Chrome/Safari之外将没有有效的堆栈。我可以接受,因为我可以在chrome中调试,但需要特定错误类型的代码仍然可以跨浏览器运行。如果你只需要Node,你可以很容易地删除if语句,你就可以开始了

片段

var CustomErrorType = function(message) {
if (Object.defineProperty) {
Object.defineProperty(this, "message", {
value : message || "",
enumerable : false
});
} else {
this.message = message;
}


if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomErrorType);
}
}


CustomErrorType.prototype = new Error();
CustomErrorType.prototype.name = "CustomErrorType";

使用

var err = new CustomErrorType("foo");

输出

var err = new CustomErrorType("foo");
console.log(err);
console.log(err.stack);


[CustomErrorType: foo]
CustomErrorType: foo
at Object.<anonymous> (/errorTest.js:27:12)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:906:3


/errorTest.js:30
throw err;
^
CustomErrorType: foo
at Object.<anonymous> (/errorTest.js:27:12)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:906:3

如果有人对如何创建自定义错误而且感兴趣,请获取堆栈跟踪:

function CustomError(message) {
this.name = 'CustomError';
this.message = message || '';
var error = new Error(this.message);
error.name = this.name;
this.stack = error.stack;
}
CustomError.prototype = Object.create(Error.prototype);


try {
throw new CustomError('foobar');
}
catch (e) {
console.log('name:', e.name);
console.log('message:', e.message);
console.log('stack:', e.stack);
}

我使用构造函数模式来创建新的错误对象。我定义了原型链,例如Error实例。参见MDN 错误的构造函数参考。

你可以在gist上检查这个片段。

实现

// Creates user-defined exceptions
var CustomError = (function() {
'use strict';


//constructor
function CustomError() {
//enforces 'new' instance
if (!(this instanceof CustomError)) {
return new CustomError(arguments);
}
var error,
//handles the arguments object when is passed by enforcing a 'new' instance
args = Array.apply(null, typeof arguments[0] === 'object' ? arguments[0] : arguments),
message = args.shift() || 'An exception has occurred';


//builds the message with multiple arguments
if (~message.indexOf('}')) {
args.forEach(function(arg, i) {
message = message.replace(RegExp('\\{' + i + '}', 'g'), arg);
});
}


//gets the exception stack
error = new Error(message);
//access to CustomError.prototype.name
error.name = this.name;


//set the properties of the instance
//in order to resemble an Error instance
Object.defineProperties(this, {
stack: {
enumerable: false,
get: function() { return error.stack; }
},
message: {
enumerable: false,
value: message
}
});
}


// Creates the prototype and prevents the direct reference to Error.prototype;
// Not used new Error() here because an exception would be raised here,
// but we need to raise the exception when CustomError instance is created.
CustomError.prototype = Object.create(Error.prototype, {
//fixes the link to the constructor (ES5)
constructor: setDescriptor(CustomError),
name: setDescriptor('JSU Error')
});


function setDescriptor(value) {
return {
configurable: false,
enumerable: false,
writable: false,
value: value
};
}


//returns the constructor
return CustomError;
}());

使用

CustomError构造函数可以接收许多参数来构建消息,例如:

var err1 = new CustomError("The url of file is required"),
err2 = new CustomError("Invalid Date: {0}", +"date"),
err3 = new CustomError("The length must be greater than {0}", 4),
err4 = new CustomError("Properties .{0} and .{1} don't exist", "p1", "p2");


throw err4;

这是自定义错误的样子:

自定义错误原型链

根据Joyent的说法你不应该混淆堆栈属性(我在这里给出的许多答案中看到),因为它会对性能产生负面影响。他们是这么说的:

栈:一般,不要乱搞这个。甚至不要扩大它。V8只在有人实际读取属性时才计算它,这极大地提高了可处理错误的性能。如果读取属性只是为了扩充它,即使调用者不需要堆栈,最终也会付出代价。

我喜欢并想要提到他们对原始错误的包装,它是传递堆栈的一个很好的替代品。

下面是我如何创建一个自定义错误,考虑到上面提到的:

es5版本:

function RError(options) {
options = options || {}; // eslint-disable-line no-param-reassign
this.name = options.name;
this.message = options.message;
this.cause = options.cause;


// capture stack (this property is supposed to be treated as private)
this._err = new Error();


// create an iterable chain
this.chain = this.cause ? [this].concat(this.cause.chain) : [this];
}
RError.prototype = Object.create(Error.prototype, {
constructor: {
value: RError,
writable: true,
configurable: true
}
});


Object.defineProperty(RError.prototype, 'stack', {
get: function stack() {
return this.name + ': ' + this.message + '\n' + this._err.stack.split('\n').slice(2).join('\n');
}
});


Object.defineProperty(RError.prototype, 'why', {
get: function why() {
var _why = this.name + ': ' + this.message;
for (var i = 1; i < this.chain.length; i++) {
var e = this.chain[i];
_why += ' <- ' + e.name + ': ' + e.message;
}
return _why;
}
});


// usage


function fail() {
throw new RError({
name: 'BAR',
message: 'I messed up.'
});
}


function failFurther() {
try {
fail();
} catch (err) {
throw new RError({
name: 'FOO',
message: 'Something went wrong.',
cause: err
});
}
}


try {
failFurther();
} catch (err) {
console.error(err.why);
console.error(err.stack);
console.error(err.cause.stack);
}

es6版本:

class RError extends Error {
constructor({name, message, cause}) {
super();
this.name = name;
this.message = message;
this.cause = cause;
}
[Symbol.iterator]() {
let current = this;
let done = false;
const iterator = {
next() {
const val = current;
if (done) {
return { value: val, done: true };
}
current = current.cause;
if (!val.cause) {
done = true;
}
return { value: val, done: false };
}
};
return iterator;
}
get why() {
let _why = '';
for (const e of this) {
_why += `${_why.length ? ' <- ' : ''}${e.name}: ${e.message}`;
}
return _why;
}
}


// usage


function fail() {
throw new RError({
name: 'BAR',
message: 'I messed up.'
});
}


function failFurther() {
try {
fail();
} catch (err) {
throw new RError({
name: 'FOO',
message: 'Something went wrong.',
cause: err
});
}
}


try {
failFurther();
} catch (err) {
console.error(err.why);
console.error(err.stack);
console.error(err.cause.stack);
}

我把我的解决方案放入一个模块,这里是:https://www.npmjs.com/package/rerror

在ES2015中,你可以使用class干净地做到这一点:

class NotImplemented extends Error {
constructor(message = "", ...args) {
super(message, ...args);
this.message = message + " has not yet been implemented.";
}
}

这不会修改全局Error原型,允许你自定义messagename和其他属性,并正确地捕获堆栈。它也很有可读性。

当然,如果你的代码将运行在旧的浏览器上,你可能需要使用像babel这样的工具。

MDN有很好的例子:

try {
throw new Error('Whoops!');
} catch (e) {
console.log(e.name + ': ' + e.message);
}

这是我的实现:

class HttpError extends Error {
constructor(message, code = null, status = null, stack = null, name = null) {
super();
this.message = message;
this.status = 500;


this.name = name || this.constructor.name;
this.code = code || `E_${this.name.toUpperCase()}`;
this.stack = stack || null;
}


static fromObject(error) {
if (error instanceof HttpError) {
return error;
}
else {
const { message, code, status, stack } = error;
return new ServerError(message, code, status, stack, error.constructor.name);
}
}


expose() {
if (this instanceof ClientError) {
return { ...this };
}
else {
return {
name: this.name,
code: this.code,
status: this.status,
}
}
}
}


class ServerError extends HttpError {}


class ClientError extends HttpError { }


class IncorrectCredentials extends ClientError {
constructor(...args) {
super(...args);
this.status = 400;
}
}


class ResourceNotFound extends ClientError {
constructor(...args) {
super(...args);
this.status = 404;
}
}

示例用法#1:

app.use((req, res, next) => {
try {
invalidFunction();
}
catch (err) {
const error = HttpError.fromObject(err);
return res.status(error.status).send(error.expose());
}
});

示例用法#2:

router.post('/api/auth', async (req, res) => {
try {
const isLogged = await User.logIn(req.body.username, req.body.password);


if (!isLogged) {
throw new IncorrectCredentials('Incorrect username or password');
}
else {
return res.status(200).send({
token,
});
}
}
catch (err) {
const error = HttpError.fromObject(err);
return res.status(error.status).send(error.expose());
}
});

我喜欢这样做:

  • 使用的名字,因此toString()抛出"{code}: {message}"
  • 将相同的东西返回给super,这样在stacktrace中就会显示相同的内容
  • 将代码附加到error.code,因为检查/解析代码比检查消息更好,例如,您可能想要本地化消息
  • 附加消息到error.message作为error.toString()的替代

class AppException extends Error {
constructor(code, message) {
const fullMsg = message ? `${code}: ${message}` : code;
super(fullMsg);
this.name = code;
this.code = code;
this.message = fullMsg;
}
  

toString() {
return this.message;
}
}


// Just a code
try {
throw new AppException('FORBIDDEN');
} catch(e) {
console.error(e);
console.error(e.toString());
console.log(e.code === 'FORBIDDEN');
}


// A code and a message
try {
throw new AppException('FORBIDDEN', 'You don\'t have access to this page');
} catch(e) {
console.error(e);
console.error(e.toString());
console.log(e.code === 'FORBIDDEN');
}

下面的代码摘自Mozilla官方文档错误

function NotImplementedError(message) {
var instance = new Error(message);
instance.name = 'NotImplementedError';


Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
if (Error.captureStackTrace) {
Error.captureStackTrace(instance, NotImplementedError);
}
return instance;
}


NotImplementedError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});

这是最快的方法:

    let thisVar = false


if (thisVar === false) {
throw new Error("thisVar is false. It should be true.")
}
class NotImplementedError extends Error {
constructor(message) {
super(message);
this.message = message;
}
}
NotImplementedError.prototype.name = 'NotImplementedError';
module.exports = NotImplementedError;

而且

try {
var e = new NotImplementedError("NotImplementedError message");
throw e;
} catch (ex1) {
console.log(ex1.stack);
console.log("ex1 instanceof NotImplementedError = " + (ex1 instanceof NotImplementedError));
console.log("ex1 instanceof Error = " + (ex1 instanceof Error));
console.log("ex1.name = " + ex1.name);
console.log("ex1.message = " + ex1.message);
}

它只是 answer的类表示。

输出

NotImplementedError: NotImplementedError message
...stacktrace
ex1 instanceof NotImplementedError = true
ex1 instanceof Error = true
ex1.name = NotImplementedError
ex1.message = NotImplementedError message

以下是我支持es2015之前版本浏览器的解决方案。它不做任何花哨的原型调整,也不会破坏调试器。

/**  Custom Errors
// Depends on underscore js
// This will declare an CustError() class in both 'this' and '_exports' namespaces
// ctor is optional
declare_cust_error(function CustError(){}, {ns: [this, _exports], ctor:
function cust_err_ctor(instance, clazz, name, msg, info){
q$.called(arguments)
}
})


// Usage:
// Second param (pojso) is optional
try {
throw CustError.create("foo", {k1: 'v1', k2: 'v2'})
}catch(ex){
if(CustError.is_inst(ex)){
console.error("its a CustError", ex)
} else {
throw ex
}
}


**/
function declare_cust_error(error_class, opts){
var p, c, cp
if(!error_class||!(p=error_class.prototype))throw new Error("error_class must be a Class")
try{
c = p.constructor; cp = c.toString()
}catch(ex){}
if(!cp || cp.indexOf('function ') != 0 || cp.indexOf('[native code]') > 0)
throw new Error("error_class must be a classic proto class (pre-es6) but got: " + error_class.toString())


opts=opts||{}
    

error_class.__is_cust_error__ = true
error_class.__cust_error_name__ = c.name


error_class.create = function cust_error_create(msg, info){
var instance = new Error(msg)
instance.info = info
instance.__is_cust_error__ = true
instance.__cust_error_name__ = c.name
if(_.isFunction(opts.ctor)){
opts.ctor(instance, error_class, c.name, msg, info)
}
return instance
}


error_class.is_inst = function cust_error_is_inst(instanace){
return ( (instanace instanceof Error) && instanace.__cust_error_name__ === error_class.__cust_error_name__ )
}
    

// Declare error in namespace(s)
_.each(_.isArray(opts.ns)?opts.ns:[opts.ns], function(ns){ ns[c.name] = error_class })


return error_class


}