Why an array of more than 10 elements in Promise.all generates the problem "No overload matches this call"?

Viewed 593

Pls consider the following code (just as dummy example, as it is, for the purpose of the question):

class Image {
 protected data: any;
 public static setUp = async (_h: any): Promise<Image> => {
    const me: Image = new Image();
    me.data = await new Promise((resolve) => setTimeout(resolve, 500));
    return me;
 };
}

class Image2 {
 protected data: any;
 public static setUp = async (_h: any): Promise<Image2> => {
    const me: Image2 = new Image2();
    me.data = await new Promise((resolve) => setTimeout(resolve, 500));
    return me;
 };
}
class Image3 {
 protected data: any;
 public static setUp = async (_h: any): Promise<Image3> => {
    const me: Image3 = new Image3();
    me.data = await new Promise((resolve) => setTimeout(resolve, 500));
    return me;
 };
}

async function testing() {
 await Promise.all([
    Image.setUp({}), // 1st
    Image2.setUp({}), // 2nd
    Image3.setUp({}),
    Image3.setUp({}),
    Image3.setUp({}), // 5th
    Image3.setUp({}),
    Image3.setUp({}),
    Image3.setUp({}),
    Image3.setUp({}),
    Image3.setUp({}), //10th
    Image3.setUp({}), // from 11th we will get the following Problem reported
 ]);
}

The problem description

No overload matches this call.
  The last overload gave the following error.
    Argument of type '(Promise<Image> | Promise<Image2> | Promise<Image3>)[]' is not assignable to parameter of type 'Iterable<Image | PromiseLike<Image>>'.
      The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
        Type 'IteratorResult<Promise<Image> | Promise<Image2> | Promise<Image3>, any>' is not assignable to type 'IteratorResult<Image | PromiseLike<Image>, any>'.
          Type 'IteratorYieldResult<Promise<Image> | Promise<Image2> | Promise<Image3>>' is not assignable to type 'IteratorResult<Image | PromiseLike<Image>, any>'.
            Type 'IteratorYieldResult<Promise<Image> | Promise<Image2> | Promise<Image3>>' is not assignable to type 'IteratorYieldResult<Image | PromiseLike<Image>>'.
              Type 'Promise<Image> | Promise<Image2> | Promise<Image3>' is not assignable to type 'Image | PromiseLike<Image>'.
                Type 'Promise<Image2>' is not assignable to type 'Image | PromiseLike<Image>'.
                  Type 'Promise<Image2>' is not assignable to type 'PromiseLike<Image>'.
                    Types of property 'then' are incompatible.
                      Type '<TResult1 = Image2, TResult2 = never>(onfulfilled?: ((value: Image2) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<...>) | null | undefined) => Promise<...>' is not assignable to type '<TResult1 = Image, TResult2 = never>(onfulfilled?: ((value: Image) => 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 'Image2' is not assignable to type 'Image'.
                              Property 'data' is protected but type 'Image2' is not a class derived from 'Image'.ts(2769)
lib.es2015.iterable.d.ts(226, 5): The last overload is declared here.
(property) Image3.setUp: (_h: any) => Promise<Image3>

I have noticed the following:

  • the problem is not just related to number of elements in the array of Promises but also on the number of distinct types returned, in fact it won't report the problem if an array with length>10 will contain Promise that will return the same type
await Promise.all([
        Image3.setUp({}), // 1st
        Image3.setUp({}), // 2nd
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}), // 5th
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}), //10th
        Image3.setUp({}), // NO ISSUES from here
        Image3.setUp({}),
    ]);

Looks like the overload defined for Promise.all in type def are not managing mixed types after a length of ten elements.

A quick fix that I've found working is to nest Promise.all like this

    async function testing() {
    await Promise.all([
        Promise.all([
        Image.setUp({}), // 1st
        Image2.setUp({}), // 2nd
        ]),
        Promise.all([
        Image2.setUp({}), // 3rd
        Image3.setUp({}),
        Image3.setUp({}), // 5th
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}),
        Image3.setUp({}), //10th
        Image3.setUp({}),
        ]),
    ]);
    }

The above will extend the max of 10 elements returning mixed types.

The problem links to this line at interface PromiseConstructor into lib.es2015.iterable type def:

    all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;

I'd like to understand more the insight of this: where the type definition fails ? Is it something related to version lib.es2015 solved on following ones? Is it something related to PromiseLike? Why problem raises since 11th element?

1 Answers

This is a limitation in how the type for Promise.all used to be defined. Initially typescript could not model such functions very well so the many overloads approach was used.

However Typescript keeps getting better all the type and now with improvements like rest tuple types, mapped tuple type and a host of perf improvements, starting with Typescript 4.5 your code will work without an issue.

You can try typescript in the playground or you can upgrade to 4.5 as it is already available on npm

Related