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)?
Ответы
Ответ 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');
Ответ 5
Начиная с TypeScript 2.2 это можно сделать через new.target.prototype
.
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example
class CustomError extends Error {
constructor(message?: string) {
super(message); // 'Error' breaks prototype chain here
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
}