Skip to content

Promise.all sometimes infers incorrect typesΒ #52601

Closed as not planned
Closed as not planned

Description

Bug Report

πŸ”Ž Search Terms

Promise.all, Awaited

πŸ•— Version & Regression Information

4.9.5

⏯ Playground Link

I couldn't repro this bug in the playground.

Please see this test repo instead.

πŸ’» Code

Multiple files are needed to repro this bug. See the test repo. The relevant error is:

export async function testPromiseAll(docs: string[]) {
  const requests = docs.map(async doc => fetch("/doc"));
  const responses = await Promise.all(requests);
  for (const res of responses) {
    await res.blob(); // Property 'blob' does not exist on type 'Promise<Response>'.
  }
}

πŸ™ Actual behavior

Code fails to build with:

Property 'blob' does not exist on type 'Promise<Response>'.

This is because the return type of Promise.all() becomes a Promise<Array<Promise<Response>>> instead of a Promise<Array<Response>>.

πŸ™‚ Expected behavior

Code typechecks.

Activity

  1. JavaScriptBach commented on Feb 4, 2023

    @JavaScriptBach
    Author

    Possibly also related to #51993

  2. RyanCavanaugh commented on Feb 4, 2023

    @RyanCavanaugh
    Member

    We'd need some way to reproduce this

  3. ritschwumm commented on Feb 4, 2023

    @ritschwumm

    no idea whether this is helpful or even related:

    in the past (probably 4.7.x or even 4.6.x) i had a vaguely similar problem without Promise.all involved at all, just some large-ish function call expression being awaited, and tsc complaining it could not assign to the variable because the types were mismatched.

    the interesting part was, that while

    const xxx:SomeType = await something();
    

    failed to compile,

    const xxx = await something();
    const yyy:SomeType = xxx;
    

    worked perfectly fine, and at least in vscode the popup on xxx did show the expected type SomeType.
    additionally,

    const xxx:Promise<SomeType> = something();
    

    worked fine, too.

    to me the whole thing felt like the mechanism behind await did something strange after looking into the expected type derived from the variable's type annotation, but as i have no idea how the actual implementation works, my assumption is probably completely wrong :)

  4. JavaScriptBach commented on Feb 5, 2023

    @JavaScriptBach
    Author

    I think I found the issue. The problem is there are two overloads of Promise.all(), defined in separate files:

    https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2015.iterable.d.ts#L227
    https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2015.promise.d.ts#L41

    The former is intended for arrays and the latter for tuples, but arrays are sometimes getting matched with the tuple overload. Moving the definitions into the same file and declaring the array one first solved my issue.

    (Per Typescript docs, TypeScript chooses the first matching overload, but I'm not sure what this behavior is if the overloads are specified in separate files. Probably it's undefined?)

  5. JavaScriptBach commented on Feb 7, 2023

    @JavaScriptBach
    Author

    Ryan Cavanaugh (@RyanCavanaugh) I've attached a repro to the issue. Hope you or someone else on the team could take a look. Thanks!

  6. wjaspers commented on Jan 26, 2026

    @wjaspers

    This is still prevalent in TS5.9.3.

    Playground Example

    Related (to tuples):

    #51993

  7. added
    Not a DefectThis behavior is one of several equally-correct options
    and removed
    Needs More InfoThe issue still hasn't been fully clarified
    on Jan 27, 2026
  8. RyanCavanaugh commented on Jan 27, 2026

    @RyanCavanaugh
    Member

    I wouldn't consider that example a bug, since it can be solved with as const.

    Promise.resolve(['test']) // string[]
        .then(() => Promise.all([
            Promise.resolve('a'),
            Promise.resolve(['b'])
        ] as const))
        .then(([aResult, bResult]) => {
            bResult.push(aResult); // Typed incorrectly -> correctly
        });
  9. wjaspers commented on Jan 27, 2026

    @wjaspers

    Its just an oversimplification to minimize noise in the reproduction.

    The real code I encountered is several async calls working together. One returns a singular string, the rest return string[].

    What is weird to me is that this works if the Promise.all is immediately followed up by an identity function.

    When would someone naturally think to use Promise.all([...]) as const in their day to day work?

  10. fidian commented on Jan 27, 2026

    @fidian

    This is still prevalent in TS5.9.3.

    Playground Example

    Related (to tuples):

    #51993

    There's nearly identical code in these three examples. It seems strange that only one of the examples is typed incorrectly. Please forgive me, but I would love to understand why this is not considered a bug with this unusual behavior.

    1. When starting from Promise<string> it works. With Promise<string[]> , the type is lost after Promise.all() (see the difference between line 7 and line 10). Could you explain how this works?
    2. With the third example, why would .then(x => x) fix the types when x is unchanged?
    3. Why would as const be necessary? The example is dealing with an inline literal array, how can that be assigned to something else?
    4. If the call to Promise.all() is pulled out to a var and returned, the type problems go away as well. This is the exact same value as returning the result from Promise.all(), so why would the types differ?

    This behavior is inconsistent. A developer would have to employ a trick only when using Promise.all() in a promise chain when the prior resolved value is an array.

  11. typescript-bot commented on Jan 30, 2026

    @typescript-bot
    Contributor

    This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  12. wjaspers commented on Jan 30, 2026

    @wjaspers

    Ryan Cavanaugh (@RyanCavanaugh) Could you please elaborate on Tyler Akins (@fidian) 's questions?

    Type widening in the example above suggests it is hard to trust the order of Promise.all's arguments to .then(). This seems misleading.

  13. locked as resolved and limited conversation to collaborators on Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions