47

I would like to extend the host object Error to a custom UploadError class. The following example fails when I compile:

class UploadError extends Error {
    constructor(message: string, private code: number) {
        super(message);
    }

    getCode(): number {
        return this.code;
    }
}

When I run the TypeScript compiler tsc I get the following error:

UploadError.ts(1,0): A export class may only extend other classes, Error is an interface.

It seems Error is defined as an interface. If anyone knows what the name of the implementation is it would make me very happy :-)

Update: I want to use Typescripts inheritance not prototypical inheritance like I currently employ to hack around this:

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;
}
Marijn Huizendveld
  • 791
  • 2
  • 7
  • 23

11 Answers11

34

Update for TypeScript 1.6:

It's now possible to directly extend from the Error class, the code in my original answer still works, but there's no longer a need for the export declare class Error.

Original answer:

Most of the answers here don't meet my requirements. The originally accepted answer doesn't compile anymore since 0.9.5 with a duplicate identifier exception. And non of them really have a stack trace (a JavaScript issue, not TypeScript).

For me a more elegant solution is:

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;
        }
    }
}

What you can do with it:

  • new Exception("msg") instanceof Error == true
  • class SpecificException extends Exception
  • catch (e) { console.log(e.stack); }

The only limitation I found was that you have to declare it in a module, and cannot make them global. For me this isn't an issue since I think a module helps in structuring, and they are there in any application I make.

One improvement you could make is strip your custom code from the stack trace, personally I think stacktraces are only for the eyes of developers, and they know where to look, so it's no big deal for me.

Community
  • 1
  • 1
Aidiakapi
  • 6,034
  • 4
  • 33
  • 62
  • 1
    I think you can safely omit toString() – nurettin Oct 24 '14 at 12:38
  • Is there any reason you'd want to omit it though? I personally like the style "SpecificException: ExceptionMessage" format it provides. – Aidiakapi Oct 27 '14 at 17:09
  • Could you please put more details under "Update for TypeScript 1.6:"? A code example would be really helpful. – David Klempfner Aug 10 '21 at 00:25
  • 1
    @DavidKlempfner I'd recommend using the answer by @Dimitry nowadays. As for the update on 1.6, it's literally the same code as in the answer, you just remove the `export declare class Error { … }` clause. – Aidiakapi Aug 12 '21 at 08:41
25

pay attention on the new changes in Typescript 2.1 - link

So you can extend the Error class but, as a recommendation, you need manually adjust the prototype immediately after any super(...) calls:

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

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

    sayHello() {
        return "hello " + this.message;
    }
}
Dmitry
  • 1,031
  • 9
  • 16
10

Update

TypeScript 1.6 is bringing the ability to extend native types, so when this lands you should be able to use

class UploadError extends Error {
    //... calls to super and all that jazz
}

Original Answer

You can implement the error interface in TypeScript, but this won't give you access to super as you aren't using inheritance:

class UploadError implements Error {
    public name = "CustomError";

    constructor (public message: string, private code: number){

    }
}

throw new UploadError ("Something went wrong!", 123);
Fenton
  • 241,084
  • 71
  • 387
  • 401
10

I have found the following approach works:

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

The generated script looks like:

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);
Ron Buckton
  • 233
  • 1
  • 5
5

This is now possible to extend Error class version 1.6. See pull request Allow expressions in class extends clauses https://github.com/Microsoft/TypeScript/pull/3516 and issue Can not extend built in types https://github.com/Microsoft/TypeScript/issues/1168

Note that tsc won't complain anymore but your editor/IDE will until it gets updated.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
  • 1
    Note that this will no longer work in TypeScript 2.1: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work – Jough Dempsey Dec 13 '16 at 16:38
2

Extending interfaces is a breaking change documented here.

Solution: change manually prototype in your constructor.

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));
  • This is the solution that worked for me on Node.js 6.9.4. Interestingly, the recommended solution from the linked TypeScript changelog with `Object.setPrototype` didn't work. – bgerth Sep 09 '17 at 07:09
1

You can use the prototype to add functions and attributes:

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

When run with node.js, this produces the following output:

Code: 1

Error is defined in lib.d.ts as follows:

interface Error {
    name: string;
    message: string;
}

declare var Error: {
    new (message?: string): Error;
    (message?: string): Error;
    prototype: Error;
}

Other than that, I see only the obvious solution to define your own interface that extends 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());
Valentin
  • 7,874
  • 5
  • 33
  • 38
1

I know the answer has been accepted, and the solution is definitely impressive, but I really don't want that amount of code in my projects just for an exception.

Until TypeScript gets proper exceptions at the language-level somehow, with Error being so cumbersome to extend, I'm now using the following very simple solution:

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!'));

Now, my error-types are really classes - you can extend them, and you will see the correct class-name on the console when an unhandled Error is thrown; but my Error objects are not instances of those classes: the constructor does not return an instance of MyError, it just applies it's own name to the Error instance you pass to it.

This also provides a simple work-around for the issue of Error producing it's stack-trace at the point where you construct it, rather than at the point where you throw it - since the constructor signature forces you to construct a "real" Error instance.

If you need to check the type of exception in your exception-handler, grab the ['type'] property and compare it using instanceof:

try {
    // something throws new MyError(new Error('aw snap!'))
} catch (error) {
    console.log(error['type'] instanceof MyError); // => true
}

It's not ideal, but it's simple and it works.

Be aware that, if you extend MyError, you will need to implement the constructor every time and add return super(...), since the default constructor generated by TypeScript does not expect constructors that use the return-statement. It does allow them though.

mindplay.dk
  • 7,085
  • 3
  • 44
  • 54
1

Ron Buckton's solution worked for me when using TypeScript 0.8.3, but it does not compile in TypeScript 0.9.5. TypeScript generate compilation error: Duplicate identifier 'ErrorClass'. I have changed the code to make it work again:

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);
    }
}
engineforce
  • 2,840
  • 1
  • 23
  • 17
1

I found solution. Is not nice, but working as well... using eval() JS function to ignore TypeScript check.

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 { ... }
Michael Zabka
  • 465
  • 5
  • 4
1

I'm using TypeScript 1.8 but this may work for earlier versions:

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;
  }

};

Note that you must have the node typings installed in order to use Error.captureStackTrace.

Westy92
  • 19,087
  • 4
  • 72
  • 54