Using the reduce function on a Typescript tuple

I'm currently working on a function that mimics the behavior of Promise.all, except instead of running all promises in parallel, I want to pass an array of callbacks and execute them sequentially. Below is the JavaScript code for my implementation:

function promiseAllSequential(promises) {
  return promises.reduce(
    (promiseAccumulator, currentPromise) =>
      promiseAccumulator.then((resultAccumulator) =>
        currentPromise.then((currentResult) => [...resultAccumulator, currentResult])
      ),
    Promise.resolve([])
  );
}

The function signature for Promise.all has the following types:

all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;

For my function to behave similarly, I believe the signature should be something like this:

promiseAllSequential<FunctionsTuple extends readonly (() => unknown)[] | []>(
  functions: FunctionsTuple
): Promise<{
  -readonly [TupleIndex in keyof FunctionsTuple]: Awaited<ReturnType<FunctionsTuple[TupleIndex]>>;
}>

However, I am encountering difficulties in getting these types to work with my existing code. The main issue seems to be how to correctly type the reduce function when dealing with a tuple.

I have created a typescript playground highlighting the errors. Additionally, I have attempted to resolve these errors by using numerous type assertions in anothertypescript playground showcasing my best attempt at resolving the issues through multiple hacky assertions.

Is there a feature within TypeScript that could help me achieve this without relying heavily on type assertions?

Answer №1

Typescript faces limitations in inferring the necessary types to validate operations like using the reduce() method to create tuple types, especially mapped tuple types. It lacks the ability to express a general operation due to the current constraints in capturing accumulator effects with reduce(). The concept of evolving accumulator types requires higher kinded types as proposed in microsoft/TypeScript#1213. However, directly referencing generic parameters over multiple types without support for higher kinded types poses significant challenges.

// The following is not valid TypeScript
interface Array<T> {
  reduce<A extends any[], F<~ extends A['length'] | LessThan<A['length']>>>(
    this: A, 
    callbackfn: <I extends LessThan<A['length']>>(
      previousValue: F<I>, 
      currentValue: A[I], 
      currentIndex: I, 
      array: A
    ) => F<A['length']>, 
    initialValue: F<0>
  ): F<A['length'];
}

This hypothetical scenario aims to illustrate how specifying an evolving accumulator type through reduce() becomes convoluted without higher kinded types in play. Despite workarounds to mimic higher kinded types, these solutions are complex and impractical for implementation.

The compiler's current limitations make it challenging to infer the generic parameter 'F' accurately, necessitating manual specification. This complicates verifying whether callback implementations align with the requisite higher order call signature.

Ultimately, these complexities exceed the compiler's capabilities in the foreseeable future, resembling issues highlighted in discussions such as Is Typescript 4.0+ capable of functions using mapped variadic tuple types?.


Therefore, utilizing type assertions is inevitable to relax type checking and prevent compilation errors. Alternatively, employing function overloads where the implementation undergoes less stringent validation offers another approach to address this challenge.

Consider implementing at least one type assertion to navigate around the compiler's inability to infer certain types automatically:

async function promiseAllSequential<F extends (() => unknown)[]>(
  functions: readonly [...F]
) {
  return functions.reduce(
    (promiseAccumulator, currentFunction) =>
      promiseAccumulator.then(async (resultAccumulator) => {
        const result = await currentFunction();
        return [...resultAccumulator, result];
      }),
    Promise.resolve<unknown[]>([])
  ) as Promise<{
    -readonly [I in keyof F]: Awaited<ReturnType<F[I]>>;
  }>;
}

Key points to note:

  • Modified 'functions' declaration by tactically assigning a specific type instead of a union. Leveraging variadic tuple type notation can facilitate tuple type inference from the compiler.

  • Rearranged annotation on 'promiseAccumulator' for brevity but no functional difference.

  • Omitted 'as unknown' assertion that proves redundant here.

  • Let the return type be inferred based on the asserted return value agnosticly.

In essence, while cosmetic alterations enhance code readability, explicitly asserting types remains crucial to bridge the gap between intent and the compiler's understanding.

Try this code in TypeScript 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

React: The Axios response is displaying correctly on the Console but is not being rendered in the

Currently, I am facing an issue with my project where I am trying to retrieve a single object from GET (by using id). The data loads correctly in console.log but it fails to render on the DOM even after implementing React.useState() and React.useEffect(). ...

Setting attributes within an object by looping through its keys

I define an enum called REPORT_PARAMETERS: enum REPORT_PARAMETERS { DEFECT_CODE = 'DEFECT_CODE', ORGANIZATION = 'ORGANIZATION' } In addition, I have a Form interface and two objects - form and formMappers that utilize the REPOR ...

Combining two entities within nodeJS

Here is the setup I have: [{ "date": "2019-01-10T18:30:00.000Z", "time": "2019-01-11T04:37:49.587Z", "abc_Info": { "_id": "5c381da651f18d5040611eb2", "abc": 2.5, "guardian": "XYZ" } }] What I am aiming for: [{ "date": "2019-01-10T18:30:00.000Z" ...

The type 'Dispatch<any>' cannot be assigned to the type '() => null'. Error code: ts(2322)

While working on my application context, I encountered a typescript error: 'Type 'Dispatch' is not assignable to type '() => null'.ts(2322)'. I am fairly new to typescript and struggling to understand this error. Below is ...

What is the best way to handle missing values in a web application using React and TypeScript?

When setting a value in a login form on the web and no value is present yet, should I use null, undefined, or ""? What is the best approach? In Swift, it's much simpler as there is only the option of nil for a missing value. How do I handle ...

Using keyof to access properties of an object type in TypeScript

I have a complex nested object structure defined by keys, which can be illustrated as follows: const DATA = { INFO1: { details: { detail1: { value: "x" }, }, }, INFO2: { details: { detail2: { val ...

What strategies can be used to address inconsistencies between the type system and runtime behavior?

I have created a unique TypeScript type called Awaitable<T> with the goal of ensuring that Awaited<Awaitable<T>> is always equal to T. export type Awaitable<T> = | (T extends Record<'then', Function> ? never : T) ...

The intricate process of selecting and organizing data into a structured

In my code, there is an array called listSelected that gets updated after each selection in another grid. const listSelected = [{ "_id": "10", "age": 35, "name": "Paige Zamora", "gender": "female", "company": " ...

I aim to display interconnected information from various APIs in a cohesive manner

I am working with two APIs: component.ts ngOnInit(): void { this.getQueryCountriesList().subscribe(arg => { this.countryDatas = arg; }); this.getQueryNights().subscribe(obj => { this.nightDatas = obj; }); ...

Is there a way for me to retrieve the text generated by OpenAI in the completion response?

let gptResponse = await openai .createCompletion({ model: "davinci", prompt, max_tokens: 60, temperature: 0.9, presence_penalty: 0, frequency_penalty: 0.5, best_of: 1, n: 1, stre ...

What is the best way to implement global error handling for NextJS API requests?

Is there a method to set up a Global error handler that captures the stack trace of errors and sends it to an external system like NewRelic without needing to modify each individual API? This would follow the DRY principle by avoiding changes to multiple ...

Invoke the API when the value of a property in the SPFX property pane is modified

Here's a question that might sound silly, but I'll ask anyway. I have a dropdown field in my Property pane that is filled with all the lists from the current site. It's working fine. When I change the dropdown selection, it populates a pro ...

Having trouble adding the Vonage Client SDK to my preact (vite) project

I am currently working on a Preact project with Vite, but I encountered an issue when trying to use the nexmo-client SDK from Vonage. Importing it using the ES method caused my project to break. // app.tsx import NexmoClient from 'nexmo-client'; ...

Angular 6 is throwing an ERROR TypeError because it is unable to access the property 'length' of a null object

Recently delving into Angular 6, I've encountered an issue with two components: app.component.ts and products.component.ts, as well as a service file. In the products component, I am receiving a JSON response in the ngOnChanges method. My goal is to ...

The attribute 'name' cannot be found within the class 'MyComponent'

I'm a beginner in Angular2 and I have no previous knowledge of version 1. Can you help me understand why this error is occurring and guide me on how to fix it? import { Component } from 'angular2/core'; @Component ({ selector: 'my- ...

Issues with communicating between parent and child components within Angular 9 using mat-tab

As a newcomer to Angular, I've been experimenting with passing data from a parent component to a child component using @Input(), but I'm facing issues with the changes not being reflected. In my parent component, there's a select element and ...

Typescript: Maximizing efficiency and accuracy

When it comes to developing Angular2 apps using Typescript, what are the essential best practices that we should adhere to? ...

The 'ngModel' property cannot be bound to a 'textarea' element because it is not recognized as a valid property

When I run Karma with Jasmine tests, I encounter the following error message: The issue is that 'ngModel' cannot be bound since it is not recognized as a property of 'textarea'. Even though I have imported FormsModule into my app.modu ...

Exploring the Concept of Dependency Injection in Angular 2

Below is a code snippet showcasing Angular 2/Typescript integration: @Component({ ... : ... providers: [MyService] }) export class MyComponent{ constructor(private _myService : MyService){ } someFunction(){ this._mySer ...

Converting JSON data into custom object types using TypeScript

Utilizing the AlphaVantage API, I am receiving a JSON response that looks like this: { "Meta Data": { "1. Information": "Intraday (5min) open, high, low, close prices and volume", "2. Symbol": &qu ...