Search code examples
typescriptes6-promise

How does the type defination of `promise.all` work well in this case?


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]>
}>

Solution

  • 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.