Расширенные ошибки не содержат трассировки сообщений или стека

При выполнении этого фрагмента через BabelJS:

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. До сих пор это терпит неудачу.

Почему подкласс не показывает трассировку сообщения или стека?

Изменить: с использованием встроенного подкласса Chrome (благодаря @coder) отлично работает. Это не относится к Вавилону, обязательно, как в следующем примере (из @loganfsmyth на канале Babel gitter):

// 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;
}());

Ответ 1

Короче говоря, расширение с использованием кода, переведенного с помощью 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 - вспомогательная функция, введенная в script:

(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 прототипа прототипа под-класса и пытался вызвать его получатель с новым экземпляром подкласса в качестве контекста, если он существует или возвращает его значение (if ("value" in desc)), в этом случае сам конструктор Error. Он не присваивает ничего this из супервызов, так что, когда новый объект имеет правильный прототип, он не строится так, как вы ожидаете. В основном супер-вызов ничего не делает для вновь созданного объекта, просто создает новый Error, который ничем не назначен.

Если мы используем описанный выше ErrorClass, он придерживается структуры классов, как и ожидалось в Babel.

Ответ 2

Это ограничение связано с компиляцией уровня ES6 до ES5 на нижнем уровне. Узнайте больше об этом в машинописном объяснении.

В качестве обходного пути вы можете:

class QueryLimitError extends Error {
  __proto__: QueryLimitError;

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

В некоторых случаях рассматриваются Object.setPrototypeOf(this, FooError.prototype); может быть достаточно.

Вы найдете дополнительную информацию в соответствующем выпуске Github и