Подтвердить что ты не робот

Typescript - Расширение класса ошибок

Я пытаюсь создать пользовательскую ошибку с именем класса "CustomError", напечатанным на консоли вместо "Ошибка", без успеха:

class CustomError extends Error { 
    constructor(message: string) {
      super(`Lorem "${message}" ipsum dolor.`);
      this.name = 'CustomError';
    }
}
throw new CustomError('foo'); 

Выходной сигнал Uncaught Error: Lorem "foo" ipsum dolor.

Что я ожидаю: Uncaught CustomError: Lorem "foo" ipsum dolor.

Интересно, может ли это быть сделано только с помощью TS (без возиться с прототипами JS)?

4b9b3361

Ответ 1

Используете ли вы typescript версию 2.1 и переписываете ES5? Проверьте этот раздел на странице с изменением изменений для возможных проблем и обходных путей: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work

Соответствующий бит:

В качестве рекомендации вы можете вручную настроить прототип сразу после любых вызовов super (...).

class FooError extends Error {
    constructor(m: string) {
        super(m);

        // Set the prototype explicitly.
        Object.setPrototypeOf(this, FooError.prototype);
    }

    sayHello() {
        return "hello " + this.message;
    }
}

Однако любой подкласс FooError должен будет также вручную установить прототип. Для сред выполнения, которые не поддерживают Object.setPrototypeOf, вы можете вместо этого использовать __proto__.

К сожалению, эти обходные пути не будут работать в Internet Explorer 10 и ранее. Можно вручную копировать методы из прототипа на сам экземпляр (т.е. FooError.prototype), но сама цепочка прототипов не может быть исправлена.

Ответ 2

Проблема в том, что встроенный класс Javascript Error разрывает цепочку прототипов, переключая объект, который должен быть построен (например, this), на новый, другой объект, когда вы вызываете super, и этот новый объект не имеет ожидаемый прототип цепочки, т.е. это экземпляр Error, а не CustomError.

Эта проблема может быть элегантно решена с помощью 'new.target', который поддерживается начиная с Typescript 2.2, см. здесь: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html

class CustomError extends Error {
  constructor(message?: string) {
    // 'Error' breaks prototype chain here
    super(message); 

    // restore prototype chain   
    const actualProto = new.target.prototype;

    if (Object.setPrototypeOf) { Object.setPrototypeOf(this, actualProto); } 
    else { this.__proto__ = actualProto; } 
  }
}

Преимущество использования new.target заключается в том, что вам не нужно жестко кодировать прототип, как предлагают некоторые другие ответы здесь. Это также имеет то преимущество, что классы, наследуемые от CustomError, также автоматически получат правильную цепочку прототипов.

Если бы вы жестко закодировали прототип (например, Object.setPrototype(this, CustomError.prototype)), сам CustomError имел бы работающую цепочку прототипов, но любые классы, унаследованные от CustomError, были бы повреждены, например, экземпляры class VeryCustomError < CustomError не будут instanceof VeryCustomError, как ожидалось, а будут только instanceof CustomError.

Смотрите также: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200

Ответ 3

Он корректно работает в ES2015 (https://jsfiddle.net/x40n2gyr/). Скорее всего, проблема заключается в том, что компилятор TypeScript транслирует на ES5, а Error не может быть правильно подклассифицирован с использованием только функций ES5; он может быть правильно подклассифицирован только с использованием ES2015 и выше функций (class или, что более неясно, Reflect.construct). Это связано с тем, что при вызове Error как функции (а не через new или в ES2015, super или Reflect.construct) он игнорирует this и создает новый Error.

Вам, вероятно, придется жить с несовершенным выходом, пока вы не сможете настроить ES2015 или выше...

Ответ 4

Я столкнулся с той же проблемой в моем проекте typescript несколько дней назад. Чтобы он работал, я использую реализацию из MDN, используя только vanilla js. Таким образом, ваша ошибка будет выглядеть примерно так:

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

throw new CustomError('foo');