【问题标题】:Extended Errors do not have message or stack trace扩展错误没有消息或堆栈跟踪
【发布时间】:2015-08-04 19:21:12
【问题描述】:

当通过 BabelJS 运行这个 sn-p 时:

class FooError extends Error {
  constructor(message) {
    super(message);
  }
}

let error = new FooError('foo');
console.log(error, error.message, error.stack);

输出

{}

这不是我所期望的。跑步

error = new Error('foo');
console.log(error, error.message, error.stack);

生产

{} foo Error: foo
    at eval (eval at <anonymous> (https://babeljs.io/scripts/repl.js?t=2015-05-21T16:46:33+00:00:263:11), <anonymous>:24:9)
    at REPL.evaluate (https://babeljs.io/scripts/repl.js?t=2015-05-21T16:46:33+00:00:263:36)
    at REPL.compile (https://babeljs.io/scripts/repl.js?t=2015-05-21T16:46:33+00:00:210:12)
    at Array.onSourceChange (https://babeljs.io/scripts/repl.js?t=2015-05-21T16:46:33+00:00:288:12)
    at u (https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js:28:185)

这正是我想要的扩展错误。

我的目标是将Error 扩展为各种子类,并在bluebird 的catch 匹配中使用它们。到目前为止,这一切都失败了。

为什么子类没有显示消息或堆栈跟踪?

编辑: using Chrome's built-in subclassing(感谢@coder)完美运行。这不一定是 Babel 特有的,如以下示例(来自@loganfsmyth on Babel's gitter feed)所示:

// Works
new (function(){
  "use strict";
  return class E extends Error { }
}());
// Doesn't
new (function(){
  "use strict";
  function E(message){
    Error.call(this, message);
  };
  E.prototype = Object.create(Error);
  E.prototype.constructor = E;
  return E;
}());

【问题讨论】:

  • 我认为这不是 Babel 问题。如果您使用旧方法来扩展错误,您会得到相同的缺失堆栈(在 Chromium41 上)。
  • 你能确认你使用的浏览器吗?似乎适用于 chrome v42 jsfiddle.net/5e3kakqj
  • Chrome 42 上的@coder 也是如此。您的示例有效,但 Babel 版本无效。
  • @dystroy 你是对的。包括某人在 babel 的 gitter 上提出的一个最小示例,它显示了简单的假继承失败和真正的 ES6 继承工作。

标签: javascript ecmascript-6 babeljs


【解决方案1】:

简而言之,使用 babel 的转译代码进行扩展仅适用于以特定方式构建的类,而很多原生的东西似乎并不是这样构建的。 Babel 的文档警告说,扩展许多原生类无法正常工作。

您可以创建一个“手动”创建属性的缓冲区类,如下所示:

class ErrorClass extends Error {
  constructor (message) {
    super();

    if (Error.hasOwnProperty('captureStackTrace'))
        Error.captureStackTrace(this, this.constructor);
    else
       Object.defineProperty(this, 'stack', {
          value: (new Error()).stack
      });

    Object.defineProperty(this, 'message', {
      value: message
    });
  }

}

然后扩展该类:

class FooError extends ErrorClass {
  constructor(message) {
    super(message);
  }
}

为什么它没有按您的预期工作?

如果你看一下转译的内容,你会看到 babel 首先将超类的原型的副本分配给子类,然后当你调用new SubClass() 时,会调用这个函数:

_get(Object.getPrototypeOf(FooError.prototype), "constructor", this).call(this, message)

其中 _get 是注入脚本的辅助函数:

(function get(object, property, receiver) {
  var desc = Object.getOwnPropertyDescriptor(object, property);

  if (desc === undefined) {
    var parent = Object.getPrototypeOf(object);

    if (parent === null) {
      return undefined;
    } else {
      return get(parent, property, receiver);
    }
  } else if ("value" in desc) {
    return desc.value;
  } else {
    var getter = desc.get;

    if (getter === undefined) {
      return undefined;
    }

    return getter.call(receiver);
  }
});

它会查找子类原型的 constructor 属性描述符,并尝试使用新的子类实例作为上下文调用它的 getter,如果它存在或返回它的值 (if ("value" in desc)),在这种情况下Error 构造函数本身。它不会从 super 调用中为 this 分配任何内容,因此虽然新对象具有正确的原型,但它并没有按照您期望的方式构建。基本上 super 调用对新构造的对象没有任何作用,只是创建了一个新的Error,它没有分配给任何东西。

如果我们使用上面定义的ErrorClass,它确实符合 Babel 所期望的类结构。

【讨论】:

  • 这似乎是对的。您只能使用本机语法扩展某些本机对象。
  • 我(认为)当 ES2015 类在浏览器中本地运行时,这将正常工作(与转译为 ES5 相比,因为某些功能无法填充)。
【解决方案2】:

这是由于 ES6 到 ES5 的低级编译造成的限制。在 TypeScript 的 explanation 中找到更多相关信息。

作为一种解决方法,您可以这样做:

class QueryLimitError extends Error {
  __proto__: QueryLimitError;

  constructor(message) {
    const trueProto = new.target.prototype;
    super(message);
    this.__proto__ = trueProto;
  }
}

在某些情况下,Object.setPrototypeOf(this, FooError.prototype); 可能就足够了。

您可以在相应的Github issue

中找到更多信息

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-17
    • 2015-02-06
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 2018-09-19
    • 1970-01-01
    • 2011-08-12
    相关资源
    最近更新 更多