Promise.all
Challenge
Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const);
Solution
Zur Lösung dieses Problems nutzen wir wieder variadic tuple types. Hierzu erweitern wir den Funktionstypen um einen generischen Parameter, der eine Tupel aus unbekannten
Elementen darstellt.
declare function PromiseAll<T extends unknown[]>(
values: readonly [...T]
): Promise<T>;
Nun müssen wir einen Promise zurückgeben, der ein Objekt beinhaltet, in dem sich alle Promise-Typen entpackt werden.
declare function PromiseAll<T extends unknown[]>(
values: readonly [...T]
): Promise<T extends Promise<infer R> ? R : T>;
Damit der Typ jetzt für alle Tests passt, muss man dieses Entpacken des inneren Promise-Typen für jeden Typen in der Tupel anwenden.
declare function PromiseAll<T extends unknown[]>(
values: readonly [...T]
): Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>;
Eine weitere, fortgeschrittenere Lösung ist das Arbeiten mit einem Akkumulator, um über die Tupel zu iterieren (mittels infer), und für jedes Element den Wert zu entpacken und im Akkumulator speichern, den wir am Ende zurückgeben.
//Extrahiert den Typen aus einem Promise mittels infer
type ExtractPromiseType<T> = T extends Promise<infer R> ? R : T;
// Laufet ueber alle Elemente der Tupel und ruft fuer jedes Element den Hilfstyp 'Extract... ' auf, um den zu erwartenden Wert zu erhalten.
type IterateTuple<
T extends readonly any[],
Acc extends any[] = []
> = T extends [infer H, ...infer R]
? IterateTuple<R, [...Acc, ExtractPromiseType<H>]>
: Acc;
declare function PromiseAll<T extends readonly any[]>(
values: readonly [...T]
): Promise<IterateTuple<T>>;