Repository navigation
4.0: Cannot use variadic tuples in Promise.all #39788
Description
Activity
I'm guessing the type definitions for Promise.all haven't been updated to take advantage of variadic tuples. I'm not sure of the process here, can I submit a PR to update them targeting the 4.0 branch?
Here's some code using a new definition for Promise.all where it works almost as expected:
declare const foo: Promise<string>; declare const bar: Promise<number>[]; declare interface PromiseConstructor { all<T extends readonly unknown[]>(values: readonly [...T]): Promise<[...T]> } async function m() { const p = await Promise.all([ foo, ...bar ]) }
For some reason
phas a type of[Promise<string>, ...Promise<number>[]]but maybe I'm doing something wrong.EDIT: Followup, I got it working!
declare const foo: Promise<string>; declare const bar: Promise<number>[]; declare interface PromiseConstructor { all<T extends readonly unknown[]>(values: [...T]): { [P in keyof T]: T[P] extends Promise<infer U> ? U : never } } async function m() { const p = await Promise.all([ foo, ...bar ]) }
Here's a working definition:
type Awaited<T> = T extends PromiseLike<infer U> ? U : T; declare function all<T extends unknown[]>(values: readonly [...T]): Promise<{ [P in keyof T]: Awaited<T[P]> }>;
We have yet to update the
Promise.alldefinition to use variadic tuples. It's not clear that we can without breaking some code, specifically in cases where type arguments are explicitly specified:let p = Promise.all<string, number>([p1, p2]);
With the variadic tuple definition that would have to be written
let p = Promise.all<[string, number]>([p1, p2]);
Ron Buckton (@rbuckton) Anything to add?
Anders Hejlsberg (@ahejlsberg)
Here's a working definition:
Ha, you must have commented just as I figured out the definition on my own - good timing!
Would be a shame to not be able to make this change as it seems like a perfect use case for variadic tuples in the standard library.
I wonder if the variadic tuple form could be added as another overload?
I've had concerns about
Awaited<T>in general because it doesn't match the ECMAScript behavior ofawaitor the "recursive unwrap" nature of native Promises, sinceAwaited<T>can't be defined recursively. Also, I tried something like this in #39336 and ran into issues: https://github.com/typescript-bot/TypeScript/pull/54/files#diff-0e62d568d28b82cee9e66b2b8fafd290R7.The issue we run into is that if we move the variadic tuple version first in the overload list (so that we use it when performing inference for the type arguments), then we run into issues with existing
Promise.all<[string, number]>([p1, p2])whenp1is aPromise<[string, number]>, especially when that type comes from further inference. If we move it last, then we don't end up using it at all. The problem is that a tuple here can have two meanings:- The type of each promise in the array (i.e.
[Promise.resolve("a"), Promise.resolve(1)]) - The type of all promises in the array (i.e.
[Promise.resolve(["a", 1]), Promise.resolve(["b", 2])])
I'm not sure we can properly fix one without breaking the other, unless we can modify the inference and overload resolution algorithms in a non-breaking way.
- The type of each promise in the array (i.e.
Ron Buckton (@rbuckton) Regarding
Awaited<T>, I think it is fine to just definite it with a manually unrolled list of unwrappings up to some maximum level. For example:type Awaited<T> = T extends PromiseLike<infer U> ? Awaited4<U> : T; type Awaited4<T> = T extends PromiseLike<infer U> ? Awaited3<U> : T; type Awaited3<T> = T extends PromiseLike<infer U> ? Awaited2<U> : T; type Awaited2<T> = T extends PromiseLike<infer U> ? Awaited1<U> : T; type Awaited1<T> = T extends PromiseLike<infer U> ? U : T;
It's not exactly clear to me what would break by putting an overload with the definition above (which is different from the one you had in your PR) first in the list. I think it would just permit
Promise.all<[string, number]>(...)to either take a tuple or an array of tuples. But maybe I'm missing something.There were a number of things
awaited Twas intending to solve thatAwaited<T>couldn't, such as recursive unwrap, handling malformed self-recursive and mutually recursive promise types, handling non-promisethenables, etc. Also, the conditional type logic doesn't work correctly when not in--strictNullChecksmode for cases likeAwaited<undefined>(at least, it didn't back in 3.7 when we were looking at some other community PR's intending to introduceAwaited<T>). I'm still investigatingawaited Tas a possibility for a future version.It's worth pointing out that the declarations in Anders Hejlsberg (@ahejlsberg)'s comment have the generic params inferred as
T[P][]and notAwaited<T[P]>[]eg.type Awaited<T> = T extends PromiseLike<infer U> ? U : T; declare function all<T extends unknown[]>(values: readonly [...T]): Promise<{ [P in keyof T]: Awaited<T[P]> }>; declare const p1: Promise<number>[]; declare const p2: Promise<string>; all([p2, ...p1]) // function all<[Promise<string>, ...Promise<number>[]]>
Ron Buckton (@rbuckton) what is the downside of doing a
Promiseunwrap this way?type Promise<A> = A extends globalThis.Promise<any> ? A : globalThis.Promise<A> type test = Promise<Promise<Promise<"typescript">>> // Promise<"typescript">
Reacted by Andrew M and Toni Villenaspideythewebhead commented
on Aug 16, 2020 More actionsHello, i have one question
wait<T extends Promise<any>[]>(...promises: T): Promise< array of T array types>can we somehow do that? example
wait(Promise.resolve(0), Promise.resolve('test')) => result type = [number, string]
because for now i got this
type PromiseResult<T> = { status: 'resolved' | 'rejected' value?: T error?: any } type PromiseResultMap<T> = { [P in keyof T]: T[P] extends Promise<infer U> ? PromiseResult<U> : PromiseResult<never> } wait<T extends Promise<any>[]>(...promises: T): Promise<PromiseResultMap<T>>The PromiseResultMap in vscode intellisense resolves as an array but the actual type is an Object with keys the indexes of the array, but i can not do something like
results.lengthor destruct it[result1, result2]because ofresults is not iterableReacted by Vladyslav Yazykov- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Aug 18, 2020 - addedRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Dec 11, 2020 1 remaining item
Pantelis Tsakoulis (@spideythewebhead), something like this?
function all<T extends any[]>(...values: { [K in keyof T]: PromiseLike<T[K]> }) { return Promise.all(values) as Promise<T>; }
FYI variadic tuple support within
Promise.allis being experimented here #39796RyanCavanaugh commented
on Feb 4, 2022 MemberMore actionsThis works now!
Reacted by Toni Villena, Tom Sherman and Artur Klesun
TypeScript Version: 4.0.0-beta
Search Terms:
variadic tuples promise.all
Code
Expected behavior:
pshould have type[string, ...number]Actual behavior:
Type error
Playground Link: https://www.typescriptlang.org/play/?ts=4.0.0-dev.20200728&ssl=9&ssc=2&pln=1&pc=1#code/CYUwxgNghgTiAEYD2A7AzgF3gMyUgXPAAoxIC2AlmiADyYwUoDmAfANwCwAUKJLAsnRYARrEIlyVWigCuZYSBgsA2gF1OXblDQBPFGBwz9GCqnhkAFAEp4Ab27xHiVJngAHeAF54UAO5QKLAlKagA6KAgIC2UHJzjcJAAaWLjHUPTRGBT4VStuAF8gA
Related Issues: none