Typescript - Extending Error class

JavascriptTypescript

Javascript Problem Overview


I'm trying to throw a custom error with my "CustomError" class name printed in the console instead of "Error", with no success:

class CustomError extends Error { 
    constructor(message: string) {
      super(`Lorem "${message}" ipsum dolor.`);
      this.name = 'CustomError';
    }
}
throw new CustomError('foo'); 

The output is Uncaught Error: Lorem "foo" ipsum dolor.

What I expect: Uncaught CustomError: Lorem "foo" ipsum dolor.

I wonder if that can be done using TS only (without messing with JS prototypes)?

Javascript Solutions


Solution 1 - Javascript

Are you using typescript version 2.1, and transpiling to ES5? Check this section of the breaking changes page for possible issues and workaround: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work

The relevant bit: > As a recommendation, you can 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; > } > } > > > However, any subclass of FooError will have to manually set the prototype as well. For runtimes that don't support Object.setPrototypeOf, you may instead be able to use __proto__. > > Unfortunately, these workarounds will not work on Internet Explorer 10 and prior. One can manually copy methods from the prototype onto the instance itself (i.e. FooError.prototype onto this), but the prototype chain itself cannot be fixed.

Solution 2 - Javascript

The problem is that Javascript's built-in class Error breaks the prototype chain by switching the object to be constructed (i.e. this) to a new, different object, when you call super and that new object doesn't have the expected prototype chain, i.e. it's an instance of Error not of CustomError.

This problem can be elegantly solved using 'new.target', which is supported since Typescript 2.2, see here: 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; } 
  }
}

Using new.target has the advantage that you don't have to hardcode the prototype, like some other answers here proposed. That again has the advantage that classes inheriting from CustomError will automatically also get the correct prototype chain.

If you were to hardcode the prototype (e.g. Object.setPrototype(this, CustomError.prototype)), CustomError itself would have a working prototype chain, but any classes inheriting from CustomError would be broken, e.g. instances of a class VeryCustomError < CustomError would not be instanceof VeryCustomError as expected, but only instanceof CustomError.

See also: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200

Solution 3 - Javascript

It works correctly in ES2015 (https://jsfiddle.net/x40n2gyr/). Most likely, the problem is that the TypeScript compiler is transpiling to ES5, and Error cannot be correctly subclassed using only ES5 features; it can only be correctly subclassed using ES2015 and above features (class or, more obscurely, Reflect.construct). This is because when you call Error as a function (rather than via new or, in ES2015, super or Reflect.construct), it ignores this and creates a new Error.

You'll probably have to live with the imperfect output until you can target ES2015 or higher...

Solution 4 - Javascript

As of TypeScript 2.2 it can be done via 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
        this.name = 'CustomError';
        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    }
}

Solution 5 - Javascript

I ran into the same problem in my typescript project a few days ago. To make it work, I use the implementation from MDN using only vanilla js. So your error would look something like the following:

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

It doesn't seem to work in SO code snippet, but it does in the chrome console and in my typescript project:

enter image description here

Solution 6 - Javascript

I literally never post on SO, but my team is working on a TypeScript project, and we needed to create many custom error classes, while also targeting es5. It would have been incredibly tedious to do the suggested fix in every single error class. But we found that we were able to have a downstream effect on all subsequent error classes by creating a main custom error class, and having the rest of our errors extend that class. Inside of that main error class we did the following to have that downstream effect of updating the prototype:

class MainErrorClass extends Error {
  constructor() {
    super()
    Object.setPrototypeOf(this, new.target.prototype)
  }
}

class SomeNewError extends MainErrorClass {} 

...

Using new.target.prototype was the key to getting all of the inheriting error classes to be updated without needing to update the constructor of each one.

Just hoping this saves someone else a headache in the future!

Solution 7 - Javascript

Try this...

class CustomError extends Error { 

  constructor(message: string) {
    super(`Lorem "${message}" ipsum dolor.`)
  }

  get name() { return this.constructor.name }

}

throw new CustomError('foo')

Solution 8 - Javascript

I was having this problem in a nodejs server. what worked for me was to transpile down to es2017 in which these issues seems to be fixed.

Edit tsconfig to


    "target": "es2017"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondarksoulsongView Question on Stackoverflow
Solution 1 - JavascriptVidarView Answer on Stackoverflow
Solution 2 - JavascriptKristian HanekampView Answer on Stackoverflow
Solution 3 - JavascriptT.J. CrowderView Answer on Stackoverflow
Solution 4 - JavascriptGrabofusView Answer on Stackoverflow
Solution 5 - JavascriptMμ.View Answer on Stackoverflow
Solution 6 - JavascriptEmiPixiView Answer on Stackoverflow
Solution 7 - JavascriptflcoderView Answer on Stackoverflow
Solution 8 - Javascriptuser12575927View Answer on Stackoverflow