162

I'd like to create my own error class in TypeScript, extending core Error to provide better error handling and customized reporting. For example, I want to create an HttpRequestError class with url, response and body passed into its constructor, which reponds with Http request to http://example.com failed with status code 500 and message: Something went wrong and proper stack trace.

How to extend core Error class in TypeScript? I've already found post in SO: How do I extend a host object (e.g. Error) in TypeScript but this solution doesn't work for me. I use TypeScript 1.5.3

Any ideas?

2
  • 1
    In what way do those answers not help you? You can't extend the Error class yet. That's coming in 1.6. Commented Jul 25, 2015 at 14:45
  • @DavidSherret I have some compilation errors which, as I see, haven't been reported by tsc in earlier versions. Commented Jul 25, 2015 at 19:09

6 Answers 6

232

TypeScript 2.1 had a breaking changes regarding Extending built-ins like Error.

From the TypeScript breaking changes documentation

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

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

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

Then you can use:

let error = new FooError("Something really bad went wrong");
if(error instanceof FooError){
   console.log(error.sayHello());
}
Sign up to request clarification or add additional context in comments.

7 Comments

It's worth mentioning that Object.setPrototypeOf needs to be called immediately after any super(...) calls.
Why do we need to add Object.setPrototypeOf(this, FooError.prototype);?
I needed to make sure tsconfig.json had "target": "es6".
@Searene If you click the "TypeScript breaking changes documentation" link it explains why.
The link does not work anymore.
|
31

Until 1.6 rolls around, I've just been making my own extendable classes.

class BaseError {
    constructor () {
        Error.apply(this, arguments);
    }
}

BaseError.prototype = new Error();

class HttpRequestError extends BaseError {
    constructor (public status: number, public message: string) {
        super();    
    }
}

var error = new HttpRequestError(500, 'Server Error');

console.log(
    error,
    // True
    error instanceof HttpRequestError,
    // True
    error instanceof Error
);

2 Comments

Yeah, my solutions are familiar, the only thing I wondered about is how to extend core classes in the same way as project's ones. Saddly, I don't see any date when TS 1.6 could be released. So, well, I think your solution is the closest to my expectations so far, thanks! :)
BaseError cannot define methods with class syntax, this way they will be replaced with BaseError.prototype = new Error().
24

This does not appear to be an issue in the current latest version of typescript (4.7.3):

import { expect } from 'chai';
import { describe } from 'mocha';

class MyError extends Error {}

describe('Custom error class "MyError"', () => {
  it('should be an instance of "MyError"', () => {
    try {
      throw new MyError();
    } catch (e) {
      expect(e).to.be.an.instanceOf(MyError);
    }
  });
});

I am unsure what version changed the behaviour but this test fails on 2.1.

3 Comments

I concur, similar test works for me on Node 18 and Typescript 4.9. I also could not find the issue or changelog where this behaviour started working again, only the original announcement that it would break back in TS 2.2.
The code certainly seems to work fine without the Object.setPrototypeOf call. It would be great though if someone could find an official reference regarding when and how this became obsolete.
This is the real modern answer. It became unnecessary with modern ES targets which use the class syntax in compiled output code rather than transpiling it into manual constructors.
23

I am using TypeScript 1.8 and this is how I use custom error classes:

UnexpectedInput.ts

class UnexpectedInput extends Error {

  public static UNSUPPORTED_TYPE: string = "Please provide a 'String', 'Uint8Array' or 'Array'.";

  constructor(public message?: string) {
    super(message);
    this.name = "UnexpectedInput";
    this.stack = (<any> new Error()).stack;
  }

}

export default UnexpectedInput;

MyApp.ts

import UnexpectedInput from "./UnexpectedInput";

...

throw new UnexpectedInput(UnexpectedInput.UNSUPPORTED_TYPE);

For TypeScript versions older than 1.8, you need to declare Error:

export declare class Error {
  public message: string;
  public name: string;
  public stack: string;
  constructor(message?: string);
}

2 Comments

You should be careful with this approach. I thought I read that calling the stack property is EXPENSIVE and should be avoided in your code. I think you might be adding a significant amount of overhead for you custom errors. From the documentation "The string representing the stack trace is lazily generated when the error.stack property is accessed."
I don't understand why this is necessary in the first place: this.stack = (<any> new Error()).stack; Should be inherited from the Error class, yes?
19

For Typescript 3.7.5 this code provided a custom error class that also captured the correct stack information. Note instanceof does not work so I use name instead

// based on https://gunargessner.com/subclassing-exception

// example usage
try {
  throw new DataError('Boom')
} catch(error) {
  console.log(error.name === 'DataError') // true
  console.log(error instanceof DataError) // false
  console.log(error instanceof Error) // true
}

class DataError {
  constructor(message: string) {
    const error = Error(message);

    // set immutable object properties
    Object.defineProperty(error, 'message', {
      get() {
        return message;
      }
    });
    Object.defineProperty(error, 'name', {
      get() {
        return 'DataError';
      }
    });
    // capture where error occured
    Error.captureStackTrace(error, DataError);
    return error;
  }
}

There are some other alternatives and a discussion of the reasons.

1 Comment

You can get the name without having to define a property on it with the name explicitly hard-coded via error.constructor.name. This will return the Type name for you.
10

There is a neat library for this at https://www.npmjs.com/package/ts-custom-error

ts-custom-error allow you to create error custom Error very easily:

import { CustomError } from 'ts-custom-error'
 
class HttpError extends CustomError {
    public constructor(
        public code: number,
        message?: string,
    ) {
        super(message)
    }
}

usage:

new HttpError(404, 'Not found')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.