Home > database >  TypeScript, how to type function that converts Promise<T>[] to Promise<T[]>?
TypeScript, how to type function that converts Promise<T>[] to Promise<T[]>?

Time:03-13

suppose I have some function like:

function allPromises<T>(array: Promise<T>[]): Promise<T[]> {
  return Promise.all(array);
}

(note: this is intentionally contrived to be minimally reproducible)

If I did this, it works fine:

const p1: Promise<string> = new Promise((resolve) => resolve('1'));
const p2: Promise<string> = new Promise((resolve) => resolve('2'));
const test1 = [p1, p2];
const res1 = allPromises(test1);

no type errors and res1 is properly typed as Promise<string[]>;

but if I do:

const p1: Promise<string> = new Promise((resolve) => resolve('1'));
const p2: Promise<number> = new Promise((resolve) => resolve(2));
const test2 = [p1, p2];
const res2 = allPromises(test2);

now I get a TS complaint that:

Argument of type '(Promise<string> | Promise<number>)[]' is not assignable to parameter of type 'Promise<string>[]'.

It seems to be because test2 is typed as (Promise<number> | Promise<string>)[]

I can fix this by doing:

const test2: Promise<string | number>[] = [p1, p2];

then res2 is typed properly as Promise<(string | number)[]>

but is there a way to type my function so that it can infer the type properly and not force me to declare the type of the array explicitly in this way?

Note: I also tried overloading the function similar to how the Promise.all typing is done, but it also failed and required me to explicitly type the array for TS to accept it:

function allPromises<T1, T2>(array: [Promise<T1>, Promise<T2>]): Promise<[T1, T2]>
function allPromises<T>(array: Promise<T>[]): Promise<T[]>
function allPromises(array: Promise<unknown>[]): Promise<unknown[]> {
  return Promise.all(array);
}

and this overload also didn't help:

function allPromises<T1, T2>(array: (Promise<T1> | Promise<T2>)[]): Promise<(T1 | T2)[]>

and I'm looking to make this work in TS V3 ... I see some new types in V4.5 can be useful for this, but I am stuck in V3 for the time being.

CodePudding user response:

I made it work with

type UnwrapPromiseArr<T> = T extends Promise<infer R>[]
  ? Promise<R[]>
  : never;

function allPromises<T extends Promise<unknown>[]>(array: T): UnwrapPromiseArr<T> {
  return Promise.all(array) as UnwrapPromiseArr<T>;
}

They key is to infer the union types that initially come from the inner Array.

Playground

CodePudding user response:

Your function is just doing the same thing as Promise.all, so just define it as an alias and defer the great typing to the original function's:

const allPromises = Promise.all;

const p1: Promise<string> = new Promise((resolve) => resolve('1'));
const p2: Promise<number> = new Promise((resolve) => resolve(2));
const test2 = [p1, p2];
const res2 = allPromises(test2);

TypeScript Playground Link

  • Related