Determining the return value using Typescript inference with multiple subclasses of Error

My function is designed to return either a number, an Error, or a NegativeError:

class NegativeError extends Error {
  name = 'NegativeError'
}

function doThing (a: number): number | NegativeError | Error {
  return a < 0 ? new NegativeError() : a === 0 ? new Error() : a
}
const done = doThing() // T | NegativeError | Error

Unfortunately, when I remove the explicit return type from the function signature, TypeScript only infers T | NegativeError as the return type:

function doThing (a: number) {
  return a < 0 ? new NegativeError() : a === 0 ? new Error() : a
}
const done = doThing() // T | NegativeError

To work around this issue, I have used a constructor function to keep the return types distinct and maintain prototypical inheritance:

function NegativeError(message: string) {
  Error.call(this)
  this.message = message
  this.name = 'NegativeError'
}
NegativeError.prototype = Object.create(Error.prototype)
NegativeError.prototype.constructor = NegativeError

However, I find this solution not very readable and it results in the TypeScript error:

An outer value of 'this' is shadowed by this container
.

Is there a better way to make TypeScript correctly infer all return types?

Answer №1

It seems like you are encountering an issue with Typescript's structural type system. The NegativeError class is structurally identical to the Error class, making Typescript treat them as subtypes of each other:

// type Test = true
type Test = Error extends NegativeError ? true : false

Because Error is a subtype of NegativeError, the union NegativeError | Error can be simplified to just NegativeError. Alternatively, since NegativeError is also a subtype of Error, the union Error | NegativeError can be simplified to just Error; either way.

This situation wouldn't occur in a language with a nominal type system like Java. In Typescript, even though NegativeError is declared as a subclass with a different name, it doesn't create a distinct type. To differentiate it, you can modify its structure by refining the type of the name property:

class NegativeError extends Error {
  name: 'NegativeError' = 'NegativeError'
}

By doing this, Error is no longer a subtype of NegativeError, but NegativeError remains a subtype of Error. Consequently, the union NegativeError | Error | number will be simplified to Error | number, which should work as expected since NegativeError is still assignable to that union type.

Answer №2

One way to address the central inference issue is by encapsulating your typical error within a different subtype, creating a workaround:

class CustomError extends Error {
  name = 'CustomError'
}
class AnotherError extends Error {
  name = 'AnotherError'
}

function performAction(val: number) {
  return val < 0 ? new CustomError() : val === 0 ? new AnotherError() : val
}
const result = performAction(2) // deduces performAction(val: number): number | CustomError | AnotherError

Answer №3

When working with typescript, I discovered that using the extend keyword to determine the super class wasn't an option for me.

To work around this limitation, I decided to create a custom class NegativeError that extends the functionality of the Error class without actually utilizing the extend keyword:

class NegativeError {
  message: string
  name: 'NegativeError' = 'NegativeError'

  constructor(message: string) {
    this.message = message
  }
}
Object.setPrototypeOf(NegativeError.prototype, Error.prototype)

I want to give credit to @kaya3 for steering me in the right direction!

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

The process of extracting all arrays from a JSON object

Within my JSON object, there is a list of countries each with multiple regions stored in an array. My goal is to extract and combine all the regions into one single list. However, when I attempt to map the data, it does not consolidate all the regions as e ...

Using TypeScript with React: Specifying a type when calling a React component

How should a type be passed into a component call? Within this scenario, the render prop APICall is utilizing a type parameter named MobileSplashReturn. Although this example is functional, it seems to be causing issues with prettier, indicating that it m ...

Navigating Angular QueryList through loops

I am currently trying to gather all the images in my component and store them in an array. To achieve this, I am utilizing Angular's @ViewChildren which returns a QueryList of ElementRef: @ViewChildren('img', { read: ElementRef }) images: Q ...

Using Google OAuth2Client with Angular 4

I am encountering an issue while trying to verify the ID token for my client using Google's example. You can find the example code here. const {OAuth2Client} = require('google-auth-library'); // <-- facing issues here const client = new ...

Are multiple click events needed for identical buttons?

In my component, there is an HTML structure like this: <div id="catalogo" class="container"> <div class="row"> <div *ngFor="let artista of artistas" class="col-sm" style="mar ...

Using 'interface' declarations from TypeScript is unsupported in JS for React Native testing purposes

I have a ReactNative app and I'm attempting to create a test using Jest. The test requires classes from a native component (react-native-nfc-manager), and one of the needed classes is defined as follows export interface TagEvent { ndefMessage: N ...

Combining and Filtering Arrays of Objects with Angular

I possess: arrayBefore arrayBefore = [{name:'n1', items: [i1]}, {name:'n2', items: [i2]}, {name:'n1', items: [i3]}] I desire to craft a function: myFunction myFunction(arrayBefore) In order for it to generate: arrayAfte ...

Retrieving attributes by their names using dots in HTML

Currently working on an Angular 2 website, I am faced with the challenge of displaying data from an object retrieved from the backend. The structure of the object is as follows: { version: 3.0.0, gauges:{ jvm.memory.total.used:{ value: 3546546 }}} The is ...

Presentation of information with loading and error scenarios

How can we effectively display data in an Angular view, considering loading state and error handling? Imagine we are fetching a set of documents from our backend and need to present them in an Angular view. We want to address three possible scenarios by p ...

Exploring the method of retrieving nested JSON objects in Angular

When my API sends back a JSON response, my Angular application is able to capture it using an Interface. The structure of the JSON response appears as follows: { "release_date":"2012-03-14", "genre_relation": ...

The module has defined the component locally, however, it has not been made available for

I have developed a collaborative module where I declared and exported the necessary component for use in other modules. import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { DateslideCompone ...

Tips for getting Angular's HttpClient to return an object rather than a string?

How can I make HttpClient return the data in JSON Object format? The Angular documentation states that HttpClient should automatically parse returned JSON data as an object. However, in my project, it only returns the data as a string. Although using JSO ...

Type children are not permitted in the TypeScript container

Container is a component that extends from @material-ui/core and does not accept children of type React.ReactNode. Layout.tsx: import { Container } from "@material-ui/core"; type LayoutProps = { children: React.ReactNode; }; function Layout( ...

The issue of circular dependencies in TypeScript arises specifically within the Record type rather than in an ordinary object type

Can you explain the difference between two types, where one throws a TS error and the other does not? type ScopeItem = | string | { all: string; team: string; }; type ScopesTree = Record<string, ScopeItem | Record& ...

Enable Intellisense for my custom ES6 JavaScript modules in VS Code

Using VS Code Intellisense can greatly enhance productivity when working with internal project files, providing helpful autocompletion features and utilizing written JSDoc comments. However, my current projects involve custom JavaScript libraries stored i ...

After a loop, a TypeScript promise will be returned

I am facing a challenge in returning after all calls to an external service are completed. My current code processes through the for loop too quickly and returns prematurely. Using 'promise.all' is not an option here since I require values obtain ...

The HTTP GET request will not be duplicated or executed until another request is made

Having trouble polling data with a GET request from the API. I want to poll data every 1 second for up to 30 seconds. The issue is, angular appears to be sending requests (logging responses), but doesn't actually send a request to the server. Here ar ...

Issue with displaying selected value and options in Mat-select when using formarray - Reactive forms in Angular

I've been working on the code below to create dropdowns, but I'm having trouble getting the selected value and options to show up in the dropdowns. Can you help me figure out what's wrong with the code? Component code testForm: FormGroup; ...

Trouble with Nested Routing in React Router Version 6: Route not Rendering

I'm facing issues nesting a path within another path in react-router-dom version 6. When I try to visit the nested argument's page (/blog/1), it shows up as a blank non-styled HTML page. However, when I add a child path to the root like /blog, it ...

React Native: Javascript library with typings not recognized as a module

I am currently facing a challenge of integrating the Microsoft Playfab Javascript Library into my React Native app following the recommendations provided here. The library comes with typings and is structured as illustrated here. In my file playfabWrapper. ...