At most three active operations
Task: write mapLimited(items, limit, fn). Results must keep the input order; one error must not wipe out the other results. Check an empty list and an invalid limit. As an extension, add cancellation.
Tests: Promises, the number of concurrent operations, and errors.
Contract from the reference tests: the function is asynchronous and returns an array in the Promise.allSettled format ({status: 'fulfilled', value} or {status: 'rejected', reason}). An invalid limit throws RangeError. An optional fourth argument signal (AbortSignal) stops new items from starting — those get the rejected status.
Follow-up: how does a limit on concurrent reads differ from a limit per second? The code won't by itself stop an external API that ignores cancellation.
You will practise
- Limit the number of operations running at once
- Keep the results in order
- Separate one item's error from the whole
- Handle cancellation with an AbortSignal
Examples
- mapLimited([20, 5, 10, 1], 2, wait)→ at most 2 active at once; results in order 0, 1, 2, 3
The 3 tests
Tests ported from the source material's reference.test.ts (node:test). In the browser they run with the same set of assertions.
map: concurrency limit, order and a partial error
let active = 0, max = 0;
const result = await mapLimited([20, 5, 10, 1], 2, async (ms, i) => {
active++; max = Math.max(max, active);
await new Promise(r => setTimeout(r, ms)); active--;
if (i === 2) throw new Error('bad');
return i;
});
assert.equal(max, 2);
assert.deepEqual(result.map(r => r.status === 'fulfilled' ? r.value : 'error'), [0, 1, 'error', 3]);map: empty input, synchronous throw and limit validation
assert.deepEqual(await mapLimited([], 3, async x => x), []);
assert.equal((await mapLimited([1], 1, () => { throw new Error('sync'); }))[0].status, 'rejected');
await assert.rejects(mapLimited([], 0, async x => x), RangeError);map: abort prevents starting further I/O
const controller = new AbortController(); let calls = 0;
const result = await mapLimited([1, 2, 3], 1, async x => { calls++; controller.abort(); return x; }, controller.signal);
assert.equal(calls, 1);
assert.deepEqual(result.map(r => r.status), ['fulfilled', 'rejected', 'rejected']);Related questions
Source: AI & Agents — practice tasks — An exercise written for the study programme, not a confirmed interview task.