0 Tage Serie
typescript / expert
Snippet

Diskriminierte Promise-Ergebnisse mit `Promise.allSettled`-Narrowing

`Promise.all` ist fail-fast: ein Reject verwirft alle erfüllten Werte — falsch, sobald partieller Erfolg fachlich Sinn ergibt (Fan-out-Fetches, Batch-Writes, parallele Migrations). `Promise.allSettled` liefert pro Item eine diskriminierte Union, und das `'fulfilled' | 'rejected'`-Tag ist genau das, worauf TypeScript narrowt. Beachte `reason: unknown` — der Vertrag ist sauber: ein abgelehntes Promise kann mit allem reagieren, auch mit Nicht-Errors. Widerstehe `(reason as Error).message` und nutze stattdessen Type-Guards wie `reason instanceof Error`. Der Cast `as Settled<T>[]` ist die einzige akzeptable Stelle — der lib-Typ nutzt `any` für `reason`, eine Verschärfung auf `unknown` zieht die richtige Disziplin durch den Rest des Codes.

snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Settled<T> =
| { status: 'fulfilled'; value: T }
| { status: 'rejected'; reason: unknown };
async function partition<T>(
ps: ReadonlyArray<Promise<T>>
): Promise<{ ok: T[]; failed: unknown[] }> {
const results = await Promise.allSettled(ps);
const ok: T[] = [];
const failed: unknown[] = [];
for (const r of results as Settled<T>[]) {
if (r.status === 'fulfilled') {
ok.push(r.value); // r is { status: 'fulfilled'; value: T }
} else {
failed.push(r.reason); // r is { status: 'rejected'; reason: unknown }
}
}
return { ok, failed };
}
const { ok, failed } = await partition<number>([
Promise.resolve(1),
Promise.reject(new Error('boom')),
Promise.resolve(2),
]);
// ok : number[] = [1, 2]
// failed: unknown[] = [Error('boom')]
Erklärung
1
type Settled<T> = { status: 'fulfilled'; value: T } | { status: 'rejected'; reason: unknown };
Eigenhändig verschärfte Discriminated Union — ersetzt das `any` für `reason` aus der lib durch `unknown` und erzwingt sichere Fehlerprüfung weiter unten.
2
const results = await Promise.allSettled(ps);
Wartet auf jedes Input unabhängig vom Ausgang — kein vorzeitiges Abbrechen, jedes Promise meldet zurück.
3
if (r.status === 'fulfilled') { ok.push(r.value); }
Der literal-String-Diskriminator erlaubt TypeScript, `r` auf den Erfolgs-Zweig zu narrowen — `value: T` ist ohne Cast verfügbar.
4
failed.push(r.reason);
Bleibt absichtlich `unknown` — jeder Konsument muss per Type-Guard prüfen, bevor er es als `Error` behandelt.