What is the best way to define a function with a variable number of generic type parameters in TypeScript?

Given

type Loadable<T> = () => T
type LoadableCombinerResult<T> = { result: T }

I am looking to define types for a function that can handle multiple Loadable<ResponseDataType> inputs with different ResponseDataType for each input, along with a combiner to process the data from these loadables.

The function should manage error states and loading progress of the loadables, and call the combiner only when all loadables are successfully loaded.

While this is achievable in untyped JavaScript, I'm struggling to properly type it in TypeScript.

An example of non-variadic typing:

function useLoadableCombiner2<TResult, T1, T2>(
  combiner: (data1: T1, data2: T2) => TResult,
  loadable1: Loadable<T1>,
  loadable2: Loadable<T2>
): LoadableCombinerResult<TResult> { ... }

function useLoadableCombiner3<TResult, T1, T2, T3>(
  combiner: (data1: T1, data2: T2, data3: T3) => TResult,
  loadable1: Loadable<T1>,
  loadable2: Loadable<T2>,
  loadable3: Loadable<T3>
): LoadableCombinerResult<TResult> { ... }

function useLoadableCombiner4<TResult, T1, T2, T3, T4>(
  combiner: (data1: T1, data2: T2, data3: T3, data4: T4) => TResult,
  loadable1: Loadable<T1>,
  loadable2: Loadable<T2>,
  loadable3: Loadable<T3>,
  loadable4: Loadable<T4>
): LoadableCombinerResult<TResult> { ... }

function useLoadableCombinerN<...>(...): LoadableCombinerResult<TResult> { ... }

Is there a way to consolidate these into one TypeScript function declaration?

Perhaps using an array or typed-tuple instead of a variable number of arguments.

The objective is to pass in a variable number of loadables and then execute the typed combiner with all the data after successful loading of all inputs.

Answer №1

If you want to represent the rest parameter list in a concise way, you can utilize a generic tuple type labeled as T. This tuple type is then used within the function combiner, allowing you to easily navigate and manipulate its elements thanks to mapped types:

declare function useLoadableCombiner<R, T extends any[]>(
  combiner: (...data: T) => R,
  ...loadable: { [I in keyof T]: Loadable<T[I]> }
): LoadableCombinerResult<R>;

When invoking useLoadableCombiner, the type of

T</code can be deduced by the data passed to <code>combiner</code or alternatively inferred from the return values within <code>loadable
:

const x = useLoadableCombiner(
  //  ^? const x: LoadableCombinerResult<boolean>
  (str, num, dat) => str.length + num > dat.getTime(),
  () => "a", () => 1, () => new Date()
)

In this scenario,

T</code is recognized from <code>loadable</code as the type <code>[Loadable<string>, Loadable<number>, Loadable<Date>]
. Furthermore, even without explicit annotations for str, num, and dat, the types are understood by the type checker based on
loadable</code. Consequently, based on the evaluation of <code>combiner</code returning a boolean value, the resulting type of <code>x</code is <code>LoadableCombinerResult<boolean>
.

Access the code via 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

Is it possible for Angular 7 to disconnect from the internet?

I am looking to disable all buttons, clicks, and hyperlinks while displaying a backdrop with the message "GO ONLINE". It may come off as rude, but it is necessary. AppComponent (TS): The connectionMonitor can be used to monitor network connectivity. pr ...

Ways to invoke a slice reducer from a library rather than a React component

I've been working on a React application using the React Boilerplate CRA Template as my foundation. This boilerplate utilizes Redux with slices, which is great for updating state within a React component. However, I'm facing a challenge when try ...

Angular 1.5 component causing Typescript compiler error due to missing semi-colon

I am encountering a semi-colon error in TypeScript while compiling the following Angular component. Everything looks correct to me, but the error only appears when I insert the this.$routeConfig array: export class AppComponent implements ng.IComponentOp ...

How can I apply a custom class in Angular 2, similar to jQuery's

I've been having trouble with a very basic task... I need to assign a class to a <div> when I click on a button. After hours of searching online, I still haven't found a simple solution and all the examples I come across are too complex for ...

Steps for building a TypeScript project for server side using webpack

After configuring webpack to compile my project using TypeScript, I encountered an issue. The project is a server-side Node project that needs to be used as a linked library by another server-side project. When I compile it with webpack, I receive a window ...

Retrieve values from a multi-level nested object by using square bracket notation for each level

I am in need of a config object to display nested data. Check out the demo code for reference In the code, accessing "customer.something" is essential. There can be multiple levels of nesting, which the grid handles with field='customer.som ...

Guide on integrating a Jison generated parser within an Angular application

Using the Jison library, parsers can be created based on a specific grammar by running: $ jison calculator.jison This process is described in reference [1]. The above command generates a parser named calculator.js. Now the question arises - how to in ...

How can one properly conduct a health check on a Twilio connection using TypeScript?

How can I create an endpoint in TypeScript to verify if the Twilio connection is properly established? What would be the proper method to perform this check? Below is a snippet of my current code: private twilioClient: Twilio; ... async checkTwilio() { ...

Obtaining the value of a dropdown in Angular 6 upon initialization, not just during a change event

Currently, I am working with a select dropdown that retrieves options data and values from an array using a loop. My goal is to extract the value of the selected dropdown when the page loads without requiring a change (in this case, "Recent"). Below is t ...

What is the best way to retrieve JSON data from a raw.github URL and save it into a variable?

Suppose there is a JSON file named data.json on Github. The raw view of the file can be accessed through a URL like this: https://raw.githubusercontent.com/data.json (Please note that this URL is fictional and not real). Assume that the URL contains JSON ...

Fastest method to invoke a potentially undefined function

With a background in C#, I am familiar with the null-conditional operator which allows you to call a function while avoiding a potential null-reference exception like this: Func<int> someFunc = null; int? someInteger = someFunc?.Invoke(); // someInte ...

Encountering difficulties in generating a personalized Angular Element

Currently, I am in the process of developing a custom Component that needs to be registered to a module. Here is how it is being done: app.module.ts import { createCustomElement } from "@angular/elements"; @NgModule({ declarations: [ExtensionCompone ...

Nestjs: Accessing the request or context using a Decorator

In my current project using NestJS, I am attempting to make the executionContext accessible in a logger for the purpose of filtering logs by request. Each injectable has its own instance of a logger, and I want to maintain this setup (where the scope of t ...

How to automatically scroll to the most recently added element in an *ngFor loop using Angular 2

In my web page, I have a dynamic list rendered using an ngFor loop. Users can add or remove elements from this list by clicking on a button. What I want to achieve is to automatically scroll the browser view to the latest element added when a user clicks o ...

Draggable bar charts in Highcharts allow users to interact with the data by clicking

I'm working on creating a chart that allows for setting values by clicking and dragging. While the dragging functionality is working fine, I've run into an issue with the click event. When I click to set a new value, the draggable feature acts er ...

What is the correct way to implement ramda's ifElse function in typescript?

My attempt to enhance the typing of ifElse in Ramda, which currently has a type definition as follows: export function ifElse(fn: Pred, onTrue: Arity2Fn, onFalse: Arity2Fn): Arity2Fn; However, I quickly reached the limitations of TypeScript (or rather my ...

Mapped types: Specify mandatory properties depending on whether an array of identical objects includes a specific string value

Can an object property be set to required or optional based on the presence of a specific string in an array within the same object? type Operator = "A" | "B" type SomeStruct = { operators: Operator[]; someProp: string; // this should be ...

Error TS2339: Cannot access attribute 'reactive_support' on interface 'LocalizedStringsMethods'

Encountering the error TS2339: Property 'reactive_support' does not exist on type 'LocalizedStringsMethods' I recently updated TypeScript from version 2.6 to 2.9, and attempted import LocalizedStrings from 'react-localization&apo ...

Adjust the field type to supersede the type of the generic object

I'm looking to set the type of T (a generic) equal to the type of the listener field, so that I can have auto completion for "firstParameter" whether the listener is the default value or a custom one. Is this achievable? If not, is there another solut ...

Installing express in typings: A beginner's guide

I'm currently incorporating expressjs into my application. Following its installation using typings install express --ambient --save, I run tsc. However, two errors are returned: typings/main/ambient/express/index.d.ts(17,34): error TS2307: Unable ...