Как расширить объект хоста (например, Ошибка) в TypeScript
Я хотел бы расширить объект-хост Error
до пользовательского класса UploadError
. Следующий пример не выполняется при компиляции:
class UploadError extends Error {
constructor(message: string, private code: number) {
super(message);
}
getCode(): number {
return this.code;
}
}
Когда я запускаю компилятор TypeScript tsc
, я получаю следующую ошибку:
UploadError.ts(1,0): A export class may only extend other classes, Error is an interface.
Кажется, что Error
определяется как интерфейс. Если кто-нибудь знает, что имя реализации это сделает меня очень счастливым: -)
Обновление: я хочу использовать наследование типов наследования, а не прототипическое наследование, которое я сейчас использую, чтобы взломать это:
function UploadError (message: string, code: number) {
this.message = message;
this.code = code;
}
UploadError.prototype = new Error();
UploadError.prototype.constructor = UploadError;
UploadError.prototype.getCode = (): number => {
return this.code;
}
Ответы
Ответ 1
Я нашел, что работает следующий подход:
declare class ErrorClass implements Error {
public name: string;
public message: string;
constructor(message?: string);
}
var ErrorClass = Error;
class MyError extends ErrorClass {
public name = "MyError";
constructor (public message?: string) {
super(message);
}
}
Сгенерированный script выглядит так:
var ErrorClass = Error;
var MyError = (function (_super) {
__extends(MyError, _super);
function MyError(message) {
_super.call(this, message);
this.message = message;
this.name = "MyError";
}
return MyError;
})(ErrorClass);
Ответ 2
Обновление для TypeScript 1.6:
Теперь можно напрямую перейти из класса Error
, код в моем исходном ответе все еще работает, но больше не нужно export declare class Error
.
Оригинальный ответ:
Большинство ответов здесь не соответствуют моим требованиям. Первоначально принятый ответ больше не компилируется с 0.9.5 с использованием исключения дублирующего идентификатора. И не у них действительно есть трассировка стека (проблема с JavaScript, а не TypeScript).
Для меня более элегантное решение:
module YourModule {
export declare class Error {
public name: string;
public message: string;
public stack: string;
constructor(message?: string);
}
export class Exception extends Error {
constructor(public message: string) {
super(message);
this.name = 'Exception';
this.message = message;
this.stack = (<any>new Error()).stack;
}
toString() {
return this.name + ': ' + this.message;
}
}
}
Что вы можете с этим сделать:
-
new Exception("msg") instanceof Error == true
-
class SpecificException extends Exception
-
catch (e) { console.log(e.stack); }
Единственное ограничение, которое я обнаружил, это то, что вы должны объявить его в модуле и не можете сделать его глобальным. Для меня это не проблема, поскольку я думаю, что модуль помогает в структурировании и они есть в любом приложении, которое я делаю.
Одним из улучшений, которые вы могли бы сделать, является удаление вашего пользовательского кода из трассировки стека, лично я считаю, что stacktraces предназначены только для глаз разработчиков, и они знают, где искать, поэтому для меня это не имеет большого значения.
Ответ 3
обратите внимание на новые изменения в Typescript 2.1 - ссылка
Таким образом, вы можете расширить класс Error, но в качестве рекомендации вам необходимо вручную настроить прототип сразу после любых вызовов super (...):
class FooError extends Error {
constructor(m: string) {
super(m);
// Set the prototype explicitly.
Object.setPrototypeOf(this, FooError.prototype);
}
sayHello() {
return "hello " + this.message;
}
}
Ответ 4
Update
TypeScript 1.6 дает возможность расширять родные типы, поэтому, когда на этих землях вы сможете использовать
class UploadError extends Error {
//... calls to super and all that jazz
}
Оригинальный ответ
Вы можете реализовать интерфейс ошибки в TypeScript, но это не даст вам доступ к super
, поскольку вы не используете наследование:
class UploadError implements Error {
public name = "CustomError";
constructor (public message: string, private code: number){
}
}
throw new UploadError ("Something went wrong!", 123);
Ответ 5
Теперь можно расширить класс Error
версии 1.6. См. Запрос на перенос Разрешить выражения в классах extends https://github.com/Microsoft/TypeScript/pull/3516 и проблема Невозможно расширить встроенные типы https://github.com/Microsoft/TypeScript/issues/1168
Обратите внимание, что tsc
больше не будет жаловаться, но ваш редактор /IDE будет до тех пор, пока он не будет обновлен.
Ответ 6
Я нашел решение. Не приятно, но работает также... используя функцию eval() JS, чтобы игнорировать проверку TypeScript.
declare class ErrorClass implements Error {
public name: string;
public message: string;
constructor(message?: string);
}
eval('ErrorClass = Error');
export = Exception;
class Exception extends ErrorClass implements Error { ... }
Ответ 7
Я знаю, что ответ был принят, и решение, безусловно, впечатляет, но я действительно не хочу, чтобы этот код был в моих проектах только для исключения.
До тех пор, пока TypeScript не получит правильные исключения на уровне языка, так как ошибка настолько громоздка для расширения, теперь я использую следующее очень простое решение:
class MyError {
constructor(error: Error) {
error.name = this['constructor'].name;
error['type'] = this; // for type-checking in exception-handlers
return error;
}
}
throw new MyError(new Error('aw snap!'));
Теперь мои типы ошибок являются действительно классами - вы можете их расширить, и вы увидите правильное имя класса на консоли, когда будет отправлена необработанная ошибка; но мои объекты Error не являются экземплярами этих классов: конструктор не возвращает экземпляр MyError, он просто применяет его собственное имя к экземпляру Error, который вы передаете ему.
Это также обеспечивает простую работу по проблеме ошибки, создавая ее стек-трассировку в том месте, где вы ее создаете, а не в точке, где вы ее бросаете - поскольку подпись конструктора заставляет вас построить "реальную" Ошибка экземпляра.
Если вам нужно проверить тип исключения в обработчике исключения, возьмите свойство ['type']
и сравните его с помощью instanceof
:
try {
// something throws new MyError(new Error('aw snap!'))
} catch (error) {
console.log(error['type'] instanceof MyError); // => true
}
Он не идеален, но он прост и работает.
Имейте в виду, что если вы расширяете MyError, вам нужно будет каждый раз реализовать конструктор и добавить return super(...)
, поскольку конструктор по умолчанию, сгенерированный TypeScript, не ожидает конструкторов, которые используют оператор return. Однако это позволяет им.
Ответ 8
Решение Ron Buckton работало для меня при использовании TypeScript 0.8.3, но оно не компилируется в TypeScript 0.9.5. TypeScript генерирует ошибку компиляции: Дублирующий идентификатор ErrorClass. Я изменил код, чтобы он снова работал:
declare class ErrorClass {
public name: string;
public message: string;
constructor(message?: string);
}
// Move following line to a JavaScript
// (not TypeScript) file.
// var ErrorClass = Error;
class MyError extends ErrorClass {
public name = "MyError";
constructor (public message?: string) {
super(message);
}
}
Ответ 9
Я использую TypeScript 1.8, но это может работать для более ранних версий:
class MyError extends Error {
static name: string;
constructor(public message?: string, public extra?: number) {
super(message);
Error.captureStackTrace(this, MyError);
this.name = (this as any).constructor.name; // OR (<any>this).constructor.name;
}
};
Обратите внимание, что для использования Error.captureStackTrace
необходимо иметь node
типизацию, чтобы использовать Error.captureStackTrace
.
Ответ 10
Расширение интерфейсов - это нарушающееся изменение, зарегистрированное здесь.
Решение: измените прототип вручную в вашем конструкторе.
class MyError extends Error {
constructor(m: string) {
super(m);
// Set the prototype explicitly. If you skip this, iinstanceof will not work :-(
(<any>this).__proto__ = MyError.prototype;
}
}
console.log("Instance of works now: "+(new MyError("my error") instanceof MyError));
Ответ 11
Вы можете использовать прототип для добавления функций и атрибутов:
interface Error {
code: number;
getCode(): number;
}
Error.prototype.code = 1;
Error.prototype.getCode = function () {
return this.code;
}
var myError = new Error();
console.log("Code: " + myError.getCode());
При запуске с node.js
это производит следующий вывод:
Code: 1
Ошибка определена в lib.d.ts
следующим образом:
interface Error {
name: string;
message: string;
}
declare var Error: {
new (message?: string): Error;
(message?: string): Error;
prototype: Error;
}
Кроме этого, я вижу только очевидное решение для определения вашего собственного интерфейса, который расширяет Error
:
interface MyErrorInterface extends Error {
code: number;
getCode(): number;
}
class MyError implements MyErrorInterface {
code : number;
name : string;
message : string;
constructor(code: number, name? : string, message? : string) {
this.code = code;
this.name = name;
this.message = message;
}
getCode(): number {
return this.code;
}
}
var myError = new MyError(1);
console.log("Code: " + myError.getCode());