I found this problem when I was doing a question of type-challenges.
The following code will fail in the case 3:
declare function PromiseAll<A extends readonly unknown[]>(values: A): Promise<{
-readonly [key in keyof A]: Awaited<A[key]>
}>
// test
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)])
// except Promise<[number, number, number]>, but got Promise<number[]>
But when I change the generic readonly unknown[]
to (readonly unknown[]) | []
like below,everything will be ok.
declare function PromiseAll<A extends (readonly unknown[]) | [] /* change here */>(values: A): Promise<{
-readonly [key in keyof A]: Awaited<A[key]>
}>
// test
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)]) // Promise<[number, number, number]>
I don't understand why adding a constraint of '[]' to generic 'A' will affect the case 3. I can't associate them at all.
In addition, the correct implementation is the standard implementation of lib.es2015.promise.d.ts, which I found in vscode
// lib.es2015.promise.d.ts
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
I found this code is work fine. Because it keeps all types and their positions by converting to Tuple:
declare function PromiseAll<A extends any[]>(values: readonly [...A]): Promise<{
[key in keyof A]: Awaited<A[key]>
}>
CodePudding user response:
As the default inference behaviour for array literals passed into a function, the TypeScript team has choosen to infer array types over tuple types.
You can see it in the tooltip if you hover over the function call:
declare function PromiseAll<A extends readonly unknown[]>(values: A): void
PromiseAll([1, 2, Promise.resolve(3)])
// function PromiseAll<(number | Promise<number>)[]>(...)
The type of A
is inferred as the array type (number | Promise<number>)[]
instead of the tuple type [number, number, Promise<number>]
.
This behaviour works fine in most situations, but to type a Promise.all()
function properly, we need to infer the tuple type. There are multiple ways to do this and you have discovered two of them in your question.
But why exactly do both of these methods infer the tuple type? We can get clarification from ahejlsberg in #31434
We only infer tuple types when the contextual type is or contains a tuple-like type
And this is pretty much the important point. (readonly unknown[]) | []
works because the union in the constraint now contains a tuple type []
. This is enough to hint at the compiler to infer a tuple type.
Using readonly [...A]
also infers the tuple since it is using the variadic tuple syntax introduced in 4.0. A detailed explanation about inference with variadic tuple types can be found at PR/#39094.
When the contextual type of an array literal is a tuple type, a tuple type is inferred for the array literal. The type [...T], where T is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types.
CodePudding user response:
Basically its the narrow tuple [number, number, number]
type instead of the widened array number[]
because adding ... | []
to the type, adds a literal type []
to the context in which in the return type is defined, in this case to the Generic A
.
This is the PR that explains/implements whats happening in detail: GitHub
and your code is a perfect example for the following change introduced in the PR:
The type inferred for an element in an array literal is the widened literal type of the expression unless the element has a contextual type that includes literal types.
The return type of PromiseAll
is defined in the context of A
. A
in turn uses a literal type in its definition. therefore it consitutes a "contextual type that includes literal types" preventing the typewidening from tuple to array.