Enhance the Error class in Typescript

I have been attempting to create a custom error using my "CustomError" class to be displayed in the console instead of the generic "Error", without any success:

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

The current output is

Uncaught Error: Lorem "foo" ipsum dolor
.

However, what I am aiming for is

Uncaught CustomError: Lorem "foo" ipsum dolor
.

I am curious if achieving this is possible solely through TypeScript (without manipulating JavaScript prototypes)?

Answer №1

If you are utilizing TypeScript version 2.1 and transpiling to ES5, make sure to review this specific section on the breaking changes page for any potential issues and solutions: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work

Here is the key information:

To ensure smooth functioning, it is recommended that you adjust the prototype manually right after any super(...) calls.

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

        // Explicitly set the prototype.
        Object.setPrototypeOf(this, FooError.prototype);
    }

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

Keep in mind, every subclass of FooError will also need to manually set the prototype. If your runtime does not support Object.setPrototypeOf, you might consider using __proto__ as an alternative.

Regrettably, these workarounds may not be effective on Internet Explorer 10 and older versions. While it is possible to manually transfer methods from the prototype to the instance (i.e., FooError.prototype onto this), the issue with the prototype chain itself remains unresolved.

Answer №2

The issue arises due to the way Javascript's built-in class Error disrupts the prototype chain by altering the object being constructed (i.e. this) to a new, distinct object when super is called. This new object does not have the expected prototype chain, as it becomes an instance of Error instead of CustomError.

An elegant solution to this problem is using 'new.target', which has been available since Typescript 2.2. More information can be found here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html

class CustomError extends Error {
  constructor(message?: string) {
    // 'Error' disrupts the prototype chain at this point
    super(message); 

    // restoring the prototype chain   
    const actualProto = new.target.prototype;

    if (Object.setPrototypeOf) { Object.setPrototypeOf(this, actualProto); } 
    else { this.__proto__ = actualProto; } 
  }
}

The use of new.target eliminates the need to hardcode the prototype, unlike some previous suggestions in other answers. This approach ensures that classes inheriting from CustomError automatically inherit the correct prototype chain.

If the prototype were hardcoded (e.g.

Object.setPrototype(this, CustomError.prototype)
), although CustomError itself would have the proper prototype chain, any subclasses of CustomError could encounter issues. For example, instances of a
class VeryCustomError < CustomError
might not be recognized as instanceof VeryCustomError but only as instanceof CustomError as intended.

For more details, refer to: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200

Answer №3

Starting with TypeScript version 2.2, you can achieve this by using new.target.prototype. Click here for more information

class CustomError extends Error {
    constructor(message?: string) {
        super(message); // 'Error' stops prototype chain
        this.name = 'CustomError';
        Object.setPrototypeOf(this, new.target.prototype); // reset prototype chain
    }
}

Answer №4

The functionality is spot on in ES2015 (https://jsfiddle.net/x40n2gyr/). It seems like the issue arises from the TypeScript compiler transpiling to ES5, where Error can't be properly subclassed using only ES5 features; it requires ES2015 or higher features such as class or more obscurely, Reflect.construct. This occurs because when you invoke Error as a function (as opposed to using new, super, or Reflect.construct in ES2015), it disregards this and generates a fresh new instance of Error.

You may need to deal with the imperfect output until you're able to target ES2015 or above...

Answer №5

Although I rarely post on SO, my team recently embarked on a TypeScript project that required the creation of numerous custom error classes while targeting es5. The prospect of implementing the suggested fix in every single error class seemed overwhelmingly tedious. However, we discovered a clever solution that allowed us to efficiently update all subsequent error classes by establishing a main custom error class and having the remaining errors extend that class. Within this main error class, we utilized the following method to ensure a cascading effect of updating the prototype:

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

class SomeNewError extends MainErrorClass {} 

...

The utilization of new.target.prototype proved instrumental in automatically updating all inheriting error classes without necessitating changes to each individual constructor. Hopefully, sharing this insight will prevent others from encountering similar challenges in the future!

Answer №6

A few days ago, I encountered a similar issue in my typescript project. To solve it, I utilized the approach outlined on MDN using pure vanilla JS. Here's how your error could be structured:

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

Although it may not display properly in the SO code snippet, it functions correctly in the Chrome console and within my typescript project:

https://i.sstatic.net/boBRq.png

Answer №7

In encountering a similar issue on my nodejs server, I found success by transpiling to es2017 where these problems appeared to be resolved.

Make adjustment in tsconfig to


    "target": "es2017"

Answer №8

Give this a shot...

class SpecialError extends Error { 

  constructor(message: string) {
    super(`Ipsum "${message}" dolor sit.`)
  }

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

}

throw new SpecialError('bar')

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Does the resolve function within a Promise executor support async operations?

I'm trying to wrap my head around the following code: function myPromiseFunc() { return new Promise((resolve) => { resolve(Promise.resolve(123)); }); } We all know that the Promise.resolve method immediately resolves a Promise with a plain ...

Is there a way to simulate the parameters injected into an fs callback without actually interacting with the filesystem during testing?

I'm currently utilizing Chai, Mocha, and Sinon for my testing framework. At the moment, I have a functioning test, but I find myself having to set up a directory and populate it with files just to make my tests run successfully. This approach doesn&a ...

The functionality of CSS transitions may be affected when dynamically adding a class

Creating a custom CSS for my main div: .main { height: 0%; position: absolute; width: 100%; z-index: 100; background: whitesmoke; bottom: 0; border-radius: 15px; padding: 20px; color: gray; left: 0; right: 0; transition: height 1s e ...

Receiving a NaN output rather than a numerical value

Experiencing what seems to be a straightforward syntax error, but I'm unable to pinpoint it. Controller: $scope.startCounter = 3; $scope.startTimeouter = function (number) { $scope.startCounter = number - 1; mytimeouter = $t ...

Establishing the default tab in JavaScript

Here's the JavaScript code snippet I have: jQuery(document).ready(function($) { // filtering subcategories var theFilter = $(".filter"); var containerFrame = $(theFilter).closest(".container-frame") var filterHeight = $(".filter").children("li") ...

OpenStreetMap is failing to display the full map within the designated div

Here is the code snippet for displaying a map when clicking on a Span text, but not showing it in full: var latitude = document.querySelector('#lati').value; var longitude = document.querySelector('#longi').value; var open_address = doc ...

Animating Angular ng-template on open/close state status

I am looking to add animation when the status of my ng-template changes, but I am unable to find any information about this component... This is the code in my report.component.html <ngb-accordion (click)="arrowRotation(i)" (panelChange)="isOpen($even ...

Scroll horizontally through the number field in Chrome

I have configured a number input field with the text aligned to the right. Scenario: While testing, I discovered that selecting the entire text or using the middle mouse button to scroll within the input field allows for a slight leftward scrolling of ab ...

What is the reason behind a PHP page refresh causing a session variable to be released

In an attempt to unset a session variable after 2 minutes using unsetsession.php, I have the following code: <?php session_start(); if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > 120 ...

Breaking down React.js element

This Navigation component handles various functionalities related to user authentication and routing. import React from 'react'; import {Navbar, Nav, NavItem, Modal, Button, FormControl} from 'react-bootstrap'; import {BrowserRouter, L ...

Output in Typescript for the chosen option

A message can be sent based on the user's choice of either Now or Later. If the user selects Now, the message will be sent immediately. If Later is chosen, a date format option needs to be created for setting a future date. <label for=""& ...

The significance of using the Spread operator in React-Redux Reducers

Exploring the essence of the spread operator has been quite intriguing. Based on my interpretation of the documentation, it seems that the spread syntax essentially duplicates the existing object and then gets replaced by a new object when one is introduce ...

Having trouble with the $.post method not loading my PHP file in my

I followed a tutorial on YouTube to copy the code, adjusted the database connection and SELECT items to fit my existing DB, but I'm struggling to get the JS file to load the PHP file. When I use Chrome's "inspect" tool, it shows that the JS file ...

The css-loader is missing the required dependency peer Webpack5, causing a resolution error

Recently, I've ventured into the world of JavaScript and I'm looking to incorporate vue-audio-visual into my project. However, I encountered a perplexing error in my node console that seems unrelated. The npm error message reads as follows: code ...

The countdown timer resets upon the conditions being rendered

I have been using the 'react-timer-hook' package to create stopwatches for each order added to an array. The problem I encountered was that every stopwatch, across all components, would reset to zero and I couldn't figure out why. After a lo ...

What measures can be taken to ensure that the input checkbox is not toggled by accidentally pressing

There's a checkbox input in a dropdown menu at the top of the page that is giving me some trouble. When I select an option by clicking on it, for some reason pressing the spacebar toggles it off and on. Clicking outside once doesn't correct it, I ...

Exploring External Functions in Angular Beyond the Library

Transitioning from standard JavaScript to Angular has been a bit challenging for me, especially when working with the Google Places library (or any other asynchronous callback). Here is the code snippet: var sparkApp = angular.module('sparkApp' ...

Error Occurs While Getting Request Parameters with Ajax Post and Express.js

When utilizing ajax to send data from a JavaScript frontend to an Express.js backend server, I am having trouble retrieving the posted data in the API method of my express.js. Below is the code where I attempt to parse the request data. Your assistance i ...

Updating Variables Declared in Parent Component from a Child Component in React using NextJS - A Comprehensive Guide

After reviewing the tutorial on React: Reverse Data Flow to update the variables foodObj, input, and buttonClicked declared in the Parent Component file Main.js, using the child component <SearchAndSuggestion>, I encountered an issue. Here is a snipp ...

Select either one checkbox out of two available options

My form includes two checkboxes, allowing users to click on both of them. I'm wondering if it's possible to set it up so that only one checkbox can be selected at a time. For example, clicking on the first checkbox would turn it on while turning ...