使用 Closure Compiler 时的扩展错误

Extending Error when using Closure Compiler

我正在使用 Closure 编译器并尝试对错误 'class' 进行子类化。我有一个试图做到这一点的元函数。它看起来像这样:

/**
 * @param {string} name
 * @param {(function(new:Error)?)=} parent
 * @param {(Function?)=} constructor
 * @return {function(new:Error)}
 * @usage
 *      var MyError = subclassError('MyError', null, function (x, y) {
 *          this.message = prop1 + " " + prop2;
 *      });
 *      throw new MyError(new Error(), 1, 2);
**/
function subclassError (name, parent, constructor) {
    // allow subclassing of other errors
    if (!parent) parent = Error;
    // allow no constructor to be provided
    if (!constructor) {
        /** @this {Error} */
        constructor = function (msg) { if (msg) this.message = msg; };
    }
    /** 
     * @constructor
     * @extends {Error}
     * @param {Error} error
     * @param {...*} var_args
    **/
    var func = function (error, var_args) {
        // this check is just a guard against further errors
        // note that we don't use something like getOwnPropertyNames
        // as this won't work in older IE
        var propsToCopy = ['fileName', 'lineNumber', 'columnNumber', 
           'stack', 'description', 'number', 'message'];
        for (var i = 0; i < propsToCopy.length; i++) {
            this[propsToCopy[i]] = error[propsToCopy[i]];
        }
        this.name = name;
        constructor.apply(this, Array.prototype.slice.call(arguments, 1));
    };
    func.prototype = Object.create(parent.prototype);
    func.prototype.constructor = func;
    func.name = constructor.name;
    return func;
}

基本上,上述函数创建了 Error 的子类,调用时需要传入原生 Error 对象。它从这个对象中填充行号、堆栈跟踪等内容。但它也允许您传递其他参数。

这是一个使用示例:

/**
 * @constructor
 * @extends {Error}
 * @param {Error} err
 * @param {number} bar
 * @param {number} baz
**/
var FooError = subclassError('FooError', null, function (bar, baz) {
    this.message = "invalid bar: '" + bar + "', (using '" + baz + "')";
});

/**
 * Frobs the noid.
 * @param {number} x
 * @param {number} y
 * @throws FooError
**/
function frob (x, y) {
    if (x < 0) throw new FooError(new Error(), x, y);
}

当我像这样用 Closure 编译它时:

java -jar compiler.jar
    --compilation_level ADVANCED_OPTIMIZATIONS --warning_level VERBOSE
    --language_in ECMASCRIPT5 --language_out ECMASCRIPT3
    --js_output_file=frob.min.js frob.js

我收到以下警告:

frob.js:39: WARNING - inconsistent return type
found   : function (new:func, (Error|null), ...*): undefined
required: function (new:Error): ?
    return func;
           ^

frob.js:60: WARNING - Function FooError: called with 3
        argument(s). Function requires at least 0 argument(s) and no more
        than 0 argument(s).
    if (x < 0) throw new FooError(new Error(), x, y);

这里有一个棘手的事情是,虽然我知道(从代码中)func 对象是从 Error 派生出来的,因为 Error 的后代是传入的 我们使用 Error 作为父级,Closure 认为它是 func 的实例而不是 Error 的实例。我尝试在上面添加一个 @extends {Error} 来纠正问题,但 Closure 仍然认为 !(func instanceof Error)。这是第一次警告。

在第二个警告中,问题在于它无法识别 FooError 接受三个参数。我尝试将三个参数添加到 FooError,但由于 Closure 没有看到参数列表,因此无法找到它。

有没有办法通过告诉 Closure 更多信息来消除这些警告?或者有没有一种方法可以更简单地扩展 Closure 中的 Error,让我们可以获取行号、堆栈等?

因为您正在调用一个 return 构造函数的方法,所以我们必须有点技巧才能在编译器中进行完整的类型检查。

首先,将subclassError方法改为return未知类型,这样我们就可以独立定义类型签名了:

/**
 * @param {string} name
 * @param {(function(new:Error)?)=} parent
 * @param {(Function?)=} constructor
 * @return {?}
 **/
function subclassError (name, parent, constructor) {}

接下来,我们为FooError添加一个存根定义,以便编译器能够理解类型信息。然后我们实际给subclassError方法的结果赋值

/**
 * @constructor
 * @extends {Error}
 * @param {Error} err
 * @param {number} bar
 * @param {number} baz
 **/
var FooError = function(err, bar, baz) {};

FooError = subclassError('FooError', null, function (bar, baz) {
  this.message = "invalid bar: '" + bar + "', (using '" + baz + "')";
});

现在我们从编译器得到正确的类型检查

// The compiler will warn that FooError is called with
// the wrong number of arguments
new FooError(new Error());

See a Full Example