Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect type inference of Promise.all when spreading Promise arrays #40330

Closed
peterjuras opened this issue Aug 31, 2020 · 8 comments · Fixed by #53090
Closed

Incorrect type inference of Promise.all when spreading Promise arrays #40330

peterjuras opened this issue Aug 31, 2020 · 8 comments · Fixed by #53090
Assignees
Labels
Fix Available A PR has been opened for this issue Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@peterjuras
Copy link

peterjuras commented Aug 31, 2020

TypeScript Version: 4.0.2 (but also the version in the Playground)

Search Terms:

Promise.all type inference

Code

async function test() {

    const promiseNumber = Promise.resolve(1);
    const promiseVoid = async () => {}

    await Promise.all([
        promiseNumber,
        ...[promiseVoid()]
    ])
}

Expected behavior:

The Promise.all call should not throw any compile time errors due to type mismatches, since Promise.all simply returns the values of the array and waits until resolution if the value is a promise.

Actual behavior:

A compile time error is thrown:

No overload matches this call.
  The last overload gave the following error.
    Argument of type '(Promise<number> | Promise<void>)[]' is not assignable to parameter of type 'Iterable<number | PromiseLike<number>>'.
      The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
        Type 'IteratorResult<Promise<number> | Promise<void>, any>' is not assignable to type 'IteratorResult<number | PromiseLike<number>, any>'.
          Type 'IteratorYieldResult<Promise<number> | Promise<void>>' is not assignable to type 'IteratorResult<number | PromiseLike<number>, any>'.
            Type 'IteratorYieldResult<Promise<number> | Promise<void>>' is not assignable to type 'IteratorYieldResult<number | PromiseLike<number>>'.
              Type 'Promise<number> | Promise<void>' is not assignable to type 'number | PromiseLike<number>'.
                Type 'Promise<void>' is not assignable to type 'number | PromiseLike<number>'.
                  Type 'Promise<void>' is not assignable to type 'PromiseLike<number>'.
                    Types of property 'then' are incompatible.
                      Type '<TResult1 = void, TResult2 = never>(onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<...>' is not assignable to type '<TResult1 = number, TResult2 = never>(onfulfilled?: ((value: number) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => PromiseLike<...>'.
                        Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
                          Types of parameters 'value' and 'value' are incompatible.
                            Type 'void' is not assignable to type 'number'.(2769)

Workaround:

Setting the generic type argument of Promise.all to void | number works, but this is a step backwards after TypeScript added better Promise.all inference and it should know that the first argument that will be returned from the call will be a number:

// Workaround which throws no errors:
async function test() {

    const promiseNumber = Promise.resolve(1);
    const promiseVoid = async () => {}

    await Promise.all<number | void>([
        promiseNumber,
        ...[promiseVoid()]
    ])
}

Playground Link:

https://www.typescriptlang.org/play?#code/IYZwngdgxgBAZgV2gFwJYHsI2QUxMgCgEoYBvAKHJmpik3xgAcAndAW1RBwDkE2AjHMxgBeGAAVWHLgDpmedABsAbjgIBGIgG4qNOhAYt2nHADV0qACaiYoSLGKiAfGQC+lGrYDuwVMglSJjLAiooEANq6ntRG0jx8gswANFHRMunhsSbmVsQAuql5ROSuQA

Related Issues:

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Sep 9, 2020
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.1.1 milestone Sep 9, 2020
@timsuchanek
Copy link
Contributor

I have this issue as well.
It came up in an issue in Prisma Client

It happens in the following code:

declare function a(): Promise<number>
declare function b(): Promise<string>

async function main() {
  const x = await Promise.all(
    [
      ...[2].map(() => a()),
      b()
    ]
  )
}

Playground Link

Could variadic tuples maybe tackle this problem?

@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Dec 11, 2020
@bfricka
Copy link

bfricka commented Mar 9, 2021

I opened this same issue: #43157
But closed it 10 minutes later when I double-checked the playground and realized it wasn't specific to 4.2.

@cbdeveloper
Copy link

Is my error related to this issue?

"typescript": "^4.2.4"

image

Note: All those get...() functions return Promise<SomeType>.

With a single promise, it's all fine. But as soon as I pass more than one promise to the array, it shows the following error:

image

Full error content:

No overload matches this call.
  The last overload gave the following error.
    Argument of type '(Promise<AdminFlags> | Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>)[]' is not assignable to parameter of type 'Iterable<AdminFlags | PromiseLike<AdminFlags>>'.
      The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
        Type 'IteratorResult<Promise<AdminFlags> | Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>, any>' is not assignable to type 'IteratorResult<AdminFlags | PromiseLike<AdminFlags>, any>'.
          Type 'IteratorYieldResult<Promise<AdminFlags> | Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>>' is not assignable to type 'IteratorResult<AdminFlags | PromiseLike<AdminFlags>, any>'.
            Type 'IteratorYieldResult<Promise<AdminFlags> | Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>>' is not assignable to type 'IteratorYieldResult<AdminFlags | PromiseLike<AdminFlags>>'.
              Type 'Promise<AdminFlags> | Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>' is not assignable to type 'AdminFlags | PromiseLike<AdminFlags>'.
                Type 'Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>' is not assignable to type 'AdminFlags | PromiseLike<AdminFlags>'.
                  Type 'Promise<{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }>' is not assignable to type 'PromiseLike<AdminFlags>'.
                    Types of property 'then' are incompatible.
                      Type '<TResult1 = { all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }, TResult2 = never>(onfulfilled?: ((value: { all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }) => TResult1 | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLik...' is not assignable to type '<TResult1 = AdminFlags, TResult2 = never>(onfulfilled?: ((value: AdminFlags) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => PromiseLike<...>'.
                        Types of parameters 'onfulfilled' and 'onfulfilled' are incompatible.
                          Types of parameters 'value' and 'value' are incompatible.
                            Type '{ all: BrandItem[]; allValues: string[]; byValue: Record<string, BrandItem>; }' is missing the following properties from type 'AdminFlags': showPrices, showAdsts(2769)
lib.es2015.iterable.d.ts(226, 5): The last overload is declared here.

What could this be?

@maapteh
Copy link

maapteh commented Nov 5, 2021

Im just wondering when this gets fixed? My code cant use generic type argument of Promise.al, else type of a result 'a' can then become any of them which is not the case :)

So code below is not working:

      const [a, b, c] = await Promise.all([
        api.getFoo(locale),
        api.getBar(locale),
        ...(searchTerm
          ? [api.searchProducts(locale, searchTerm)]
          : [null]),
      ]);

version: 4.3.5

@KKrisu
Copy link

KKrisu commented May 12, 2022

Seems to be resolved now on 4.6.4

@tranminhquanq
Copy link

tranminhquanq commented May 19, 2022

You can use it this way, I tested it and it works

const promises: Promise<number, string>[] = [promiseNumber, promiseString];
const res = await Promise.all(promises);

@maapteh
Copy link

maapteh commented May 19, 2022

You dont want <number, void>, what you want is that types come from their own. But it seems to be fixed :)

@tranminhquanq
Copy link

tranminhquanq commented May 19, 2022

@maapteh Sorry for this, i tried it with types <string, number>. Yah, it seems to be fixed, so i solved my problem this way :D

@RyanCavanaugh RyanCavanaugh removed this from the TypeScript 4.8.0 milestone Feb 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fix Available A PR has been opened for this issue Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

Successfully merging a pull request may close this issue.