Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript: Promise.all doesn't handle union types

I have the following piece of code that results in compile error in TypeScript:

type Promisable = (() => Promise<string>) | (() => Promise<number>);

const func = async (promisable: Promisable) => {
  await Promise.all([promisable()]);
};

The error is as follows

No overload matches this call. The last overload gave the following error. Argument of type '(Promise | Promise)[]' is not assignable to parameter of type 'Iterable>'. The types returned by 'Symbol.iterator.next(...)' are incompatible between these types.

For the record, removing the union type works as intended:

type Promisable = () => Promise<string>;

const func = async (promisable: Promisable) => {
  await Promise.all([promisable()]);
};

You can see the error for yourself here https://www.typescriptlang.org/play/?ssl=4&ssc=3&pln=1&pc=1#code/C4TwDgpgBACgTgewLYEsDOBDARgG2gXigAoiBKKfAPlkVTQgB41g4UA7Ac0vIB9iyK1eMnSM2AVyRYIcbgG4AsACgAxgjbMoAM3FsVFKBjQg9xMLXTY8ALhojMuCOSpQA3sqiGA7hhTA7dBAAdBg4OEQA2ub2VhBkALqkikoAvnJAA

Is it not possible to use union types in combination with Promise.all?

EDIT: I know it's possible to use something like () => Promise<string|number> instead. But in an advanced application with a lot of asynchronous functions and big types, it is not easy to convert union of functions into function of union. It's not very practical from the code perspective as well.

like image 368
dotintegral Avatar asked Mar 19 '20 13:03

dotintegral


1 Answers

Update

This is one of the cases, where type inference with current promise type declarations fails. Simplest solution is to just add the generic type argument manually:

const promisable: Promisable = ...
const res = await Promise.all<string | number>([promisable()]); 
// res: (string|number)[]

You might infer string | number automatically:

type PromiseReturn<T> = T extends () => Promise<infer I> ? I : never
const res = await Promise.all<PromiseReturn<Promisable>>([promisable()]);

With TypeScript 4.1: More complex, potentially nested Promise types can be resolved and flattened with a custom recursive Awaited type like this:

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

Playground



Old answer

Update: The awaited type operator is delayed to later versions - not clear whether it will be released at all.


This is a known issue. Good news: TS 3.9 (beta soon) will come out with improved promise types:

I would like to reintroduce the awaited type operator from #17077 to meet our needs for a mechanism to recursively unwrap a Promise-like type for methods like Promise.all, Promise.race, Promise.allSettled, Promise.prototype.then, and Promise.prototype.catch.

Type declarations of Promise.all and others use the new awaited type operator. If you test with the nightly build, Promise.all now correctly resolves to Promise<(string | number)[]>:

type Promisable = (() => Promise<string>) | (() => Promise<number>);

declare const promisable: Promisable
const res = await Promise.all([promisable()]); // res: (string | number)[]

In contrast, TS 3.8 can't handle it. For versions < 3.9, you can manually assign the generic type argument:

declare const promisable: Promisable
const res = await Promise.all<string | number>([promisable()]); // res: (string | number)[]
like image 87
ford04 Avatar answered Oct 20 '22 00:10

ford04