Typescript - dynamic array of promises with different return values

1.8k Views Asked by At

Can anyone help me solve what's wrong with this code? I can't find the right type to put in that Promise.all call at the bottom. it tried also Promise.all<Services[], PullRequests[]>(ops) but PullRequests[] can't be optional...

function wait<Twait>(time: number, response: Twait): Promise<Twait> {
    return new Promise(resolve => setTimeout(() => resolve(response), time))
}

interface Service {
    name: string;
    id: number;
}

async function getServices(): Promise<Service[]>  {
    const response = await wait(400, { "id": 200 });
    return [{ name: "service", id: response.id }]
}


interface PullRequest {
    prType: string;
    githubId: number;
}

async function getPrs(): Promise<PullRequest[]>  {
    const response = await wait(400, { "githubId": 200 });
    return [{ prType: "foo", githubId: response.githubId }]
}


async function main(): Promise<void> {

    const ops: [ PromiseLike<Service[]>, PromiseLike<PullRequest[]>? ] = [getServices()]

    if (Math.random() > 0.5) { // <== this is random on purpose.
        ops.push(getPrs())
    }
    
    
    const [ services, prs ] = await Promise.all(ops) // This throws a ts compile error (attached below)
    console.log("services:")
    console.log(services)
    console.log("prs:")
    console.log(prs)    
}

No overload matches this call. The last overload gave the following error.
 Argument of type '[PromiseLike<Service[]>, (PromiseLike<PullRequest[]> | undefined)?]' is not assignable to parameter of type 'Iterable<Service[] | PromiseLike<Service[] | undefined> | undefined>'. 
The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. 
Type 'IteratorResult<PromiseLike<Service[]> | PromiseLike<PullRequest[]> | undefined, any>' is not assignable to type 'IteratorResult<Service[] | PromiseLike<Service[] | undefined> | undefined, any>'. 
Type 'IteratorYieldResult<PromiseLike<Service[]> | PromiseLike<PullRequest[]> | undefined>' is not assignable to type 'IteratorResult<Service[] | PromiseLike<Service[] | undefined> | undefined, any>'. 
Type 'IteratorYieldResult<PromiseLike<Service[]> | PromiseLike<PullRequest[]> | undefined>' is not assignable to type 'IteratorYieldResult<Service[] | PromiseLike<Service[] | undefined> | undefined>'. 
Type 'PromiseLike<Service[]> | PromiseLike<PullRequest[]> | undefined' is not assignable to type 'Service[] | PromiseLike<Service[] | undefined> | undefined'. 
Type 'PromiseLike<PullRequest[]>' is not assignable to type 'Service[] | PromiseLike<Service[] | undefined> | undefined'. 
Type 'PromiseLike<PullRequest[]>' is not assignable to type 'PromiseLike<Service[] | undefined>'. 
Type 'PullRequest[]' is not assignable to type 'Service[]'. 
Type 'PullRequest' is missing the following properties from type 'Service': name, id

1

There are 1 best solutions below

0
On BEST ANSWER

Looks like a known issue in TS library definitions, according to microsoft/TypeScript#28427. Currently the library hardcodes a bunch of specific tuple types which do not include optional elements. There has been some effort taken to fix this, but apparently PromiseLike types and how they interact with await are complicated enough that none of the pull requests fixing this have been accepted yet. A comment in microsoft/TypeScript#39788 explains that switching this might break existing real world code that depends on the current definitions.

Until and unless this gets addressed upstream, you could use declaration merging to add your own signature for Promise.all() that just maps the promise unwrapping over the elements of the passed-in array-or-tuple:

interface PromiseConstructor {
  all<T extends readonly any[]>(
    values: { [K in keyof T]: T[K] | PromiseLike<T[K]> }
  ): Promise<T>;
}

And you can test that it does what you want:

const promiseAllOps = Promise.all(ops); // no error now  
// const promiseAllOps: Promise<[Service[], (PullRequest[] | undefined)?]>
const awaitPromiseAllOps = await promiseAllOps;
// const awaitPromiseAllOps: [Service[], (PullRequest[] | undefined)?]
const [services, prs] = awaitPromiseAllOps;
services; // Service[]
prs; // PullRequest[] | undefined

Playground link