Specific TypeScript function that exclusively accepts types such as `number|undefined` and does not simply accept `number` alone

I've been working on creating a utility class that can help me throw an exception when something may be undefined, like

throwIfUndefined(array[index]).function()
and
throwIfUndefined(obj.key).function()
. My goal is to streamline my code as using if conditions and manually throwing errors can get quite cumbersome.

Here's my initial approach:

export const throwIfUndefined = <T>(v: T | undefined): T => {
  if (v === undefined) {
    throw new Error("Unexpected undefined");
  } else {
    return v;
  }
};

This method works in most cases:

const x = Math.random() < 0.5 ? undefined : 1;
throwIfUndefined(x); // works fine: type-checks correctly
throwIfUndefined(1); // issue: should trigger a type error because the input can never be undefined

How can I adjust it so TypeScript will flag unnecessary use of throwIfUndefined?

I attempted using conditional types but couldn't wrap my head around how they would fit in this scenario.

Edit: Here's an example of code that I find tedious to write:

const item = array[index];
if (!item) return;

return item.function() ? 1 : 2;

The process becomes even more laborious when dealing with multiple elements in an array. I'd prefer a shortcut like this (eliminating the extra condition and variable declaration):

return throwIfUndefined(array[index]).function() ? 1 : 2

Answer №1

Perhaps not the most ideal solution, but one way to approach this is by defining constraints on the generic type where undefined must be assignable to it. This would involve a lower bound constraint, likened to saying T super undefined instead of T extends ⋯. However, TypeScript does not have built-in support for lower bound constraints (although there is a feature request at microsoft/TypeScript#14520). You can create a workaround by implementing a recursive constraint in the following manner:

export const throwIfUndefined =
  <T extends (undefined extends T ? unknown : never)>(v: T) => {
    if (v === undefined) {
      throw new Error("Unexpected undefined");
    } else {
      return v;
    }
  };

In this function, T represents the type of v, and the return type is T & ({} | null). The type {} | null essentially encompasses "everything except undefined", making T & ({} | null) equivalent to "T with undefined excluded." Let's put it to the test:

const x = Math.random() < 0.5 ? undefined : 1;
const y = throwIfUndefined(x); // okay;
y // 1

throwIfUndefined(1); // error
// Argument of type 'number' is not assignable to parameter of type 'never'.

For the successful call, the input is of type 1 | undefined and the output is of type 1. In the case of the unsuccessful call, the compiler indicates that number cannot be assigned to never. While this error message may be slightly confusing, it is challenging to generate better feedback without resorting to invalid types or throw types, as discussed in microsoft/TypeScript#23689. If you choose to utilize this method, clear documentation will be essential in helping users understand its functionality.

Link to code on Playground

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

Leveraging React's state to enable temporary invalid numeric input handling

My current approach may be flawed, but I aim to have a parent component and a child component, where the child contains an input field for users to enter numbers. The callback function of the parent component will only be triggered for valid numbers, as ve ...

Is bundling a Node.js backend a wise decision or a mistake?

Just a thought that crossed my mind - I understand the advantages of bundling client-side code, but what about bundling server-side code with Browserify/Webpack? Is this considered a best practice? ...

Avoid making API calls in every ngOnInit() function

I am currently developing an Angular front-end for a web-based application. One of the challenges I am facing is that all sub-page drill downs, implemented as different Angular components, make identical API calls in the ngOnInit() method. This repetitiv ...

What is the most efficient approach to handle the next state after calling setState in react with immer?

Using Typescript, React, and Immer, I have set up a state management system to save profiles with multiple options. My goal is to ensure that the active profile has the correct option data before saving it. const { updateProfileList, getProfile } = useProf ...

Transform a list of H1..6 into a hierarchical structure

I have a task to convert H1 to H6 tags from a markdown file into a JavaScript hierarchy for use in a Table of Contents. The current list is generated by AstroJS and follows this format [{depth: 1, text: 'I am a H1'}, {depth: 2: 'I am a H2}] ...

How can we track and record NaN values in JavaScript/TypeScript as they occur in real-time?

Is there a reliable method to identify and prevent NaN values during runtime, throughout all areas of the application where they might arise? A) Are there effective linting tools available to alert about possible occurrences of NaN values within specific ...

Unable to find '.file.scss' in the directory '../pages'

I am currently in the process of migrating my Ionic 3 app to version 4. However, I have encountered an issue where some pages are not recognizing the SCSS file from the TypeScript component. @Component({ selector: 'car-id', templateUrl: &apo ...

Creating templates for both classes and individual objects is an essential part of object-oriented programming

I am working on a simple game project using TypeScript. My goal is to utilize interfaces to implement them in classes and pass them as arguments for creating new instances of a class. interface ObjectConstructor { element: HTMLElement; x_pos: numbe ...

The 'Alias[]' type does not share any properties with the 'Alias' type

I encountered an issue: The error message 'Type 'Alias[]' has no properties in common with type 'Alias'' appeared. Here is my Alias setup: alias: Alias = { id: 0, domain_id: 0, source: '', dest ...

Leveraging Multiple @Input Decorators in Ionic 3 and Angular 2

Within my Ionic 3 project, I have developed a custom component called my-component. Utilizing the angular @Input functionality, data can be passed to this component with ease. In this case, I have two inputs defined as: @Input('finder') myFinder ...

Is it feasible to customize Angular Material Constants within select.ts?

I am looking for a way to dynamically set the height of a select element by passing a variable, but the height is currently a constant in the material code (select.ts). Check out the mat-select documentation View the source code on Github: material2 / se ...

Determine the accurate data type while iterating through a for loop

I am facing an issue where I have around 40 unique actions defined, all with the same parameters except for each being provided with a different schema which is causing the problem type ActionName = 'replaceText' | 'replaceImage'; type ...

Steps for exporting various elements from a .vue file

In my Vue project, I am incorporating TypeScript along with Vue. There is a specific scenario where I need to export multiple items from my .vue file. Here's an example of what I want to achieve: // FooBar.vue <template> ... </template& ...

Having trouble viewing the initial value in an AngularJS2 inputText field

I'm having trouble displaying the initial value in inputText. I'm unsure of what mistake I'm making, but the value should be showing up as expected. Kind regards, Alper <input type="text" value="1" [(ngModel)]="Input.VSAT_input1" name= ...

Improving Image Loading Efficiency in Next.js - Enhancing Performance by Preloading Images

Currently, I am working on a project in Next.js that involves a scroll-based image loading component. The method I am using to fetch and load images dynamically based on the scroll position is causing performance problems and leading to a less than ideal u ...

Retrieve data from Ionic storage

Need help with Ionic storage value retrieval. Why is GET2 executing before storage.get? My brain needs a tune-up, please assist. public storageGet(key: string){ var uid = 0; this.storage.get(key).then((val) => { console.log('GET1: ' + key + ...

Exploring the differences between Angular's @Input and @Output directives and utilizing Injectable Services

When considering the differences between @Input/@Output in parent and child components versus using services that are instantiated only once with dependency injection (@Injectable()), I find myself questioning whether there are any distinctions beyond the ...

Is there a way to turn off the warning overlay in a React application?

I’m currently using react-app-rewired and I am trying to figure out how to turn off the overlay that displays Typescript warnings whenever I compile. It seems like some warnings that are not caught by the VSCode Typescript checker pop up on this overlay ...

Create interfaces for a TypeScript library that is available on npm for export

I have a project in TypeScript that I am packaging as a library to be used by both JavaScript and TypeScript projects. After compiling, I upload the .js and .d.ts files to npm. The main.ts file exports the following: interface MyInterface{ // ... } clas ...

CDK Error: Unable to locate MethodResponse in AWS API Gateway configuration

I'm facing an issue in vscode while trying to access the MethodResponse interface from apigateway. Unfortunately, I'm getting an error message: The type 'typeof import(".../node_modules/aws-cdk-lib/aws-apigateway/index")' d ...