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

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

var nie = new NotImplementedError("some message");
console.log("The message is: '"+nie.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

}

其他回答

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

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

x = new NotImplementedError();

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

尝试为用户定义的错误类型的每个实例创建一个新的原型对象。它允许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;
}

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

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

err instanceof Error err instanceof CustomErrorType console.log() returns [CustomErrorType] when created with a message console.log() returns [CustomErrorType: message] when created without a message throw/stack provides the information at the point the error was created. Works optimally in Node.JS, and Chrome. Will pass instanceof checks in Chrome, Safari, Firefox and IE 8+, but will not have a valid stack outside of Chrome/Safari. I'm OK with that because I can debug in chrome, but code which requires specific error types will still function cross browser. If you need Node only you can easily remove the if statements and you're good to go.

片段

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

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

Stack:一般来说,不要搞砸这个。甚至不要扩大它。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

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

文档 源

它的简化形式是:

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

// Later on...

throw new NotImplementedError();