Partition allSettled Results
`Promise.allSettled` is the right call for partial-success workflows, but its `{ status, value, reason }` shape is awkward to consume directly. This snippet wraps it with a partitioner that returns `{ values, errors }` so the happy path stays simple, then layers in input-aware error reports that pair each failure with the original argument. Use it for fan-out fetches, batched writes, or any spot where one bad item should not poison the whole batch.
622 views
20
async function partitionSettled(promises) {
const settled = await Promise.allSettled(promises);
const values = [];
const errors = [];
for (const r of settled) {
if (r.status === 'fulfilled') values.push(r.value);
else errors.push(r.reason);
}
return { values, errors };
}
const tasks = [
Promise.resolve(1),
Promise.reject(new Error('boom')),
Promise.resolve(3),
];
partitionSettled(tasks).then((r) => console.log(r));Promise.allSettled always resolves, never throws, so wrapping it in a tiny partitioner gives the rest of your code a clean two-bucket result. Callers can decide whether a non-empty errors array is fatal or just a warning for the caller to log. This is the easiest way to keep a fan-out endpoint partially useful when one downstream service is flaky. Time complexity is O(n) over the settled array; memory is one entry per input.
async function partitionWithInputs(inputs, fn) {
const settled = await Promise.allSettled(inputs.map((i) => fn(i)));
const values = [];
const errors = [];
settled.forEach((r, i) => {
if (r.status === 'fulfilled') values.push({ input: inputs[i], value: r.value });
else errors.push({ input: inputs[i], reason: r.reason });
});
return { values, errors };
}
function fakeFetch(id) {
if (id === 2) return Promise.reject(new Error('not found'));
return Promise.resolve(`row-${id}`);
}
partitionWithInputs([1, 2, 3], fakeFetch).then((r) => console.log(r));Knowing which input failed is usually more important than the error itself: the caller wants to retry just those items, log them, or surface them in a UI. Mapping the inputs through fn and then walking the settled array index-for-index keeps every result paired with its original argument, even when the order matters for the caller. This shape composes nicely with retry helpers (feed errors.map((e) => e.input) back through partitionWithInputs). Watch out for input arrays that contain duplicates: the index-based pairing is still correct, but the caller should treat duplicates as distinct attempts.
async function tolerantAll(inputs, fn, { maxErrorRatio = 0.5 } = {}) {
const { values, errors } = await (async () => {
const settled = await Promise.allSettled(inputs.map((i) => fn(i)));
const ok = [];
const bad = [];
settled.forEach((r) => (r.status === 'fulfilled' ? ok.push(r.value) : bad.push(r.reason)));
return { values: ok, errors: bad };
})();
if (errors.length / inputs.length > maxErrorRatio) {
const err = new Error(`too many failures: ${errors.length}/${inputs.length}`);
err.partial = values;
throw err;
}
return values;
}
function flaky(id) {
return id % 2 === 0 ? Promise.resolve(id) : Promise.reject(new Error('odd'));
}
tolerantAll([1, 2, 3, 4], flaky, { maxErrorRatio: 0.7 })
.then((vs) => console.log('kept:', vs))
.catch((e) => console.log('rejected:', e.message));Promise.allSettled will swallow every failure by default, which can mask outages. A tolerance threshold lets the partial-success pattern fail fast when the batch is mostly broken (a downstream is down, a token expired) instead of returning a tiny array that callers might treat as authoritative. Attaching the partial successful values to the error keeps the data available if the caller still wants to log or persist them. Tune maxErrorRatio to match the SLA: read paths often allow 0.5, write paths usually want 0.1 or stricter.
