typescript / expert
Snippet
Discriminated Promise Results with `Promise.allSettled` Narrowing
`Promise.all` is fail-fast: one rejection discards every fulfilled value, which is the wrong shape whenever partial success has business meaning (fan-out fetches, batch writes, parallel migrations). `Promise.allSettled` returns a discriminated union per item, and the `'fulfilled' | 'rejected'` tag is what TypeScript narrows on. Note `reason: unknown` — the contract is sound: a rejected promise can reject with anything, including non-Errors. Resist `(reason as Error).message`; type-guard with `reason instanceof Error` instead. The cast `as Settled<T>[]` is the one acceptable spot — the lib type uses `any` for `reason`, and tightening it to `unknown` propagates the right discipline through the rest of the codebase.
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')]
Breakdown
1
type Settled<T> = { status: 'fulfilled'; value: T } | { status: 'rejected'; reason: unknown };
Hand-tightened discriminated union — replaces the lib's `any` for `reason` with `unknown`, forcing safe error inspection downstream.
2
const results = await Promise.allSettled(ps);
Awaits every input regardless of outcome — no early termination, every promise reports back.
3
if (r.status === 'fulfilled') { ok.push(r.value); }
The literal-string discriminant lets TypeScript narrow `r` to the success arm, exposing `value: T` without a cast.
4
failed.push(r.reason);
Stays `unknown` deliberately — any consumer must type-guard before treating it as an `Error`.