Как расширить объект хоста (например, Ошибка) в 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());