镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

4.0: Cannot use variadic tuples in Promise.all #39788

Description

TypeScript Version: 4.0.0-beta

Search Terms:

variadic tuples promise.all

Code

declare const foo: Promise<string>;
declare const bar: Promise<number>[];

async function m() {
    const p = await Promise.all([
        foo,
        ...bar
    ])
}

Expected behavior:

p should 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

Activity

  1. tom-sherman commented on Jul 28, 2020

    @tom-sherman
    Author

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

    Playground link

    For some reason p has 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
        ])
    }

    Playground link

  2. ahejlsberg commented on Jul 28, 2020

    @ahejlsberg
    Member

    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.all definition 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?

  3. tom-sherman commented on Jul 28, 2020

    @tom-sherman
    Author

    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?

  4. rbuckton commented on Jul 28, 2020

    @rbuckton
    Contributor

    I've had concerns about Awaited<T> in general because it doesn't match the ECMAScript behavior of await or the "recursive unwrap" nature of native Promises, since Awaited<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]) when p1 is a Promise<[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:

    1. The type of each promise in the array (i.e. [Promise.resolve("a"), Promise.resolve(1)])
    2. 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.

  5. ahejlsberg commented on Jul 28, 2020

    @ahejlsberg
    Member

    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.

  6. rbuckton commented on Jul 28, 2020

    @rbuckton
    Contributor

    There were a number of things awaited T was intending to solve that Awaited<T> couldn't, such as recursive unwrap, handling malformed self-recursive and mutually recursive promise types, handling non-promise thenables, etc. Also, the conditional type logic doesn't work correctly when not in --strictNullChecks mode for cases like Awaited<undefined> (at least, it didn't back in 3.7 when we were looking at some other community PR's intending to introduce Awaited<T>). I'm still investigating awaited T as a possibility for a future version.

  7. tom-sherman commented on Jul 29, 2020

    @tom-sherman
    Author

    It's worth pointing out that the declarations in Anders Hejlsberg (@ahejlsberg)'s comment have the generic params inferred as T[P][] and not Awaited<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>[]]>
  8. millsp commented on Aug 13, 2020

    @millsp
    Contributor

    Ron Buckton (@rbuckton) what is the downside of doing a Promise unwrap this way?

    type Promise<A> =
        A extends globalThis.Promise<any>
        ? A
        : globalThis.Promise<A>
    
    type test = Promise<Promise<Promise<"typescript">>> // Promise<"typescript">
  9. spideythewebhead commented on Aug 16, 2020

    @spideythewebhead

    Hello, 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.length or destruct it [result1, result2] because of results is not iterable

  10. 1 remaining item

  11. elumixor commented on May 24, 2021

    @elumixor

    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>;
    }
  12. privatenumber commented on May 29, 2021

    @privatenumber

    FYI variadic tuple support within Promise.all is being experimented here #39796

  13. RyanCavanaugh commented on Feb 4, 2022

    @RyanCavanaugh
    Member

    This works now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Needs InvestigationThis issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions