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

Viewed 103

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.

2 Answers

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

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