Summary:
Modified async function allAndWait<T extends readonly unknown[] | []>(values: T & Iterable<PromiseLike<unknown>>): Promise<{-readonly [key in keyof T]: Awaited<T[key]>}>
async function allAndWait<T extends PromiseLike<unknown>>(values: Iterable<T>): Promise<Awaited<T>[]>
async function allAndWait<T extends readonly unknown[] | []>(values: Iterable<PromiseLike<unknown>>): Promise<any> {
const allSettled = await Promise.allSettled(values) as PromiseSettledResult<Awaited<T[number]>>[]
const rejected = allSettled.find((promise) => promise.status === 'rejected') as PromiseRejectedResult | undefined;
if (rejected) {
throw rejected.reason;
}
return allSettled.map((promise) => (promise as PromiseFulfilledResult<Awaited<T[number]>>).value)
}
My Process Explanation:
In the given code, the type inference for T
is not correct. Ideally, T
should be a union of all types in the input tuple (e.g., string | number
). We can achieve this by redefining the type within the generic declaration (moving PromiseLike<unknown>
would suffice).
async function allAndWait<T extends Iterable<PromiseLike<unknown>>>(values: T) ...
However, converting T
from a union to a tuple poses a challenge. To maintain order in return types and extract specific types, a mapped type can be utilized:
{[key in keyof T]: Awaited<T[key]>}
Initially, utilizing a rest operator seemed feasible:
async function allAndWait<T extends Promise<unknown>[]>(...values: T): Promise<{[key in keyof T]: Awaited<T[key]>}> {...}
allAndWait(Promise.resolve("qwe"), Promise.resolve(2)) // typeof Promise<[string, number]>
Yet, due to inability to use the rest operator with iterables and their lack of tuples-like behavior, an alternative approach was necessary. A workaround involved using an intersection type:
async function allAndWait<T extends readonly unknown[] | []>(values: T & Iterable<Promise<unknown>>): Promise<{-readonly [key in keyof T]: Awaited<T[key]>}> {...}
An additional overload was provided for less powerful implementations with iterable inputs:
async function allAndWait<T extends PromiseLike<unknown>>(values: Iterable<T>): Promise<Awaited<T>[]>
This less powerful overload should be placed below the primary one to ensure it does not overshadow the more specific implementation.