js-09-pool
0.143
Challenge · difficulty 5/5
# Concurrency-limited async pool
Implement an ES module **`solution.js`** (no external libraries):
```js
export async function pool(thunks, concurrency) { /* ... */ }
```
`thunks` is an array of zero-argument functions, each returning a promise (async thunks).
Run them with **at most `concurrency` running at the same time**, and resolve with an array
of their results **in the original order of `thunks`** (not completion order).
Requirements:
- The result at index `i` must be the resolved value of `thunks[i]`.
- At no point may more than `concurrency` thunks be in flight simultaneously.
- As soon as one thunk settles, the next pending thunk should start (keep the pool full).
- An empty `thunks` array resolves to `[]`.
- You may assume `concurrency >= 1`.
Notes:
- Do not simply run everything via `Promise.all(thunks.map(...))` — that ignores the limit.
- You do not need to handle rejections specially (a rejecting thunk may reject the pool).
Example:
```js
const order = [];
const make = (id, ms) => () =>
new Promise((res) => setTimeout(() => { order.push(id); res(id); }, ms));
await pool([make("a", 30), make("b", 10), make("c", 20)], 2);
// => ["a", "b", "c"] (results in original order, regardless of finish order)
```
tests/solution.test.js
import { test } from "node:test";
import { strict as assert } from "node:assert";
import { pool } from "./solution.js";
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
// Build instrumented thunks that track peak concurrency.
function makeTracker() {
const state = { active: 0, peak: 0, starts: [] };
const thunk = (id, ms) => async () => {
state.active++;
state.peak = Math.max(state.peak, state.active);
state.starts.push(id);
await sleep(ms);
state.active--;
return id;
};
return { state, thunk };
}
test("results are returned in original order, not completion order", async () => {
const { thunk } = makeTracker();
// 'a' is slowest but must still come first in the results.
const out = await pool([thunk("a", 30), thunk("b", 5), thunk("c", 15)], 3);
assert.deepEqual(out, ["a", "b", "c"]);
});
test("peak concurrency never exceeds the limit", async () => {
const { state, thunk } = makeTracker();
const thunks = [];
for (let i = 0; i < 10; i++) thunks.push(thunk(i, 10));
const out = await pool(thunks, 3);
assert.deepEqual(out, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert.ok(state.peak <= 3, `peak was ${state.peak}, expected <= 3`);
assert.equal(state.peak, 3); // pool should actually reach the limit
});
test("concurrency of 1 runs strictly sequentially", async () => {
const { state, thunk } = makeTracker();
const thunks = [thunk("x", 10), thunk("y", 10), thunk("z", 10)];
const out = await pool(thunks, 1);
assert.deepEqual(out, ["x", "y", "z"]);
assert.equal(state.peak, 1);
});
test("empty thunks -> empty array", async () => {
const out = await pool([], 4);
assert.deepEqual(out, []);
});
test("concurrency larger than number of thunks", async () => {
const { state, thunk } = makeTracker();
const thunks = [thunk(1, 5), thunk(2, 5)];
const out = await pool(thunks, 10);
assert.deepEqual(out, [1, 2]);
assert.equal(state.peak, 2);
});
test("all thunks actually run exactly once", async () => {
let count = 0;
const thunks = [];
for (let i = 0; i < 6; i++) {
thunks.push(async () => {
count++;
await sleep(2);
return i * i;
});
}
const out = await pool(thunks, 2);
assert.deepEqual(out, [0, 1, 4, 9, 16, 25]);
assert.equal(count, 6);
});
test("pool keeps the slots full as tasks finish", async () => {
// Mix of fast and slow tasks; with limit 2, a fast finisher should let a new
// task start while a slow one is still running -> peak hits 2 repeatedly.
const { state, thunk } = makeTracker();
const thunks = [
thunk("a", 40),
thunk("b", 5),
thunk("c", 5),
thunk("d", 5),
];
const out = await pool(thunks, 2);
assert.deepEqual(out, ["a", "b", "c", "d"]);
assert.equal(state.peak, 2);
});
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.
Proposed solution
```js
export async function pool(thunks, concurrency) {
const results = new Array(thunks.length);
let index = 0;
const running = new Set();
const next = () => {
while (index < thunks.length && running.size < concurrency) {
const thunk = thunks[index++];
running.add(thunk);
thunk()
.then((value) => {
results[index - 1] = value;
running.delete(thunk);
next();
})
.catch((err) => {
results[index - 1] = err;
running.delete(thunk);
next();
});
}
};
next();
return results;
}
```Test output (stdout)
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 0
1: 1
2: 2
3: 3
4: 4
5: 5
6: 6
7: 7
8: 8
9: 9
actual:
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:33:10)
async Test.run (node:internal/test_runner/test:1313:7)
async Test.processPendingSubtests (node:internal/test_runner/test:897:7)
...
# Subtest: concurrency of 1 runs strictly sequentially
not ok 3 - concurrency of 1 runs strictly sequentially
---
duration_ms: 0.176572
type: 'test'
location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:38:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
+ <3 empty items>
- 'x',
- 'y',
- 'z'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'x'
1: 'y'
2: 'z'
actual:
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:42:10)
async Test.run (node:internal/test_runner/test:1313:7)
async Test.processPendingSubtests (node:internal/test_runner/test:897:7)
...
# Subtest: empty thunks -> empty array
ok 4 - empty thunks -> empty array
---
duration_ms: 0.081201
type: 'test'
...
# Subtest: concurrency larger than number of thunks
not ok 5 - concurrency larger than number of thunks
---
duration_ms: 0.134771
type: 'test'
location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:51:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
+ <2 empty items>
- 1,
- 2
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 1
1: 2
actual:
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:55:10)
async Test.run (node:internal/test_runner/test:1313:7)
async Test.processPendingSubtests (node:internal/test_runner/test:897:7)
...
# Subtest: all thunks actually run exactly once
not ok 6 - all thunks actually run exactly once
---
duration_ms: 0.171021
type: 'test'
location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:59:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
+ <6 empty items>
- 0,
- 1,
- 4,
- 9,
- 16,
- 25
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 0
1: 1
2: 4
3: 9
4: 16
5: 25
actual:
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:70:10)
async Test.run (node:internal/test_runner/test:1313:7)
async Test.processPendingSubtests (node:internal/test_runner/test:897:7)
...
# Subtest: pool keeps the slots full as tasks finish
not ok 7 - pool keeps the slots full as tasks finish
---
duration_ms: 0.139511
type: 'test'
location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:74:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
+ <4 empty items>
- 'a',
- 'b',
- 'c',
- 'd'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'a'
1: 'b'
2: 'c'
3: 'd'
actual:
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:85:10)
async Test.run (node:internal/test_runner/test:1313:7)
async Test.processPendingSubtests (node:internal/test_runner/test:897:7)
...
1..7
# tests 7
# suites 0
# pass 1
# fail 6
# cancelled 0
# skipped 0
# todo 0
# duration_ms 66.359881
Self-repair attempts
[
{
"total": 7,
"answer": "```js\nexport async function pool(thunks, concurrency) {\n const results = new Array(thunks.length);\n let index = 0;\n const running = new Set();\n\n const next = () => {\n while (index < thunks.length && running.size < concurrency) {\n const thunk = thunks[index++];\n running.add(thunk);\n thunk()\n .then((value) => {\n results[index - 1] = value;\n running.delete(thunk);\n next();\n })\n .catch((err) => {\n results[index - 1] = err;\n running.delete(thunk);\n next();\n });\n }\n };\n\n next();\n return results;\n}\n```",
"passed": 1,
"test_error": "f thunks\nnot ok 5 - concurrency larger than number of thunks\n ---\n duration_ms: 0.134771\n type: 'test'\n location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:51:1'\n failureType: 'testCodeFailure'\n error: |-\n Expected values to be strictly deep-equal:\n + actual - expected\n \n [\n + <2 empty items>\n - 1,\n - 2\n ]\n \n code: 'ERR_ASSERTION'\n name: 'AssertionError'\n expected:\n 0: 1\n 1: 2\n actual:\n operator: 'deepStrictEqual'\n stack: |-\n TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:55:10)\n async Test.run (node:internal/test_runner/test:1313:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:897:7)\n ...\n# Subtest: all thunks actually run exactly once\nnot ok 6 - all thunks actually run exactly once\n ---\n duration_ms: 0.171021\n type: 'test'\n location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:59:1'\n failureType: 'testCodeFailure'\n error: |-\n Expected values to be strictly deep-equal:\n + actual - expected\n \n [\n + <6 empty items>\n - 0,\n - 1,\n - 4,\n - 9,\n - 16,\n - 25\n ]\n \n code: 'ERR_ASSERTION'\n name: 'AssertionError'\n expected:\n 0: 0\n 1: 1\n 2: 4\n 3: 9\n 4: 16\n 5: 25\n actual:\n operator: 'deepStrictEqual'\n stack: |-\n TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:70:10)\n async Test.run (node:internal/test_runner/test:1313:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:897:7)\n ...\n# Subtest: pool keeps the slots full as tasks finish\nnot ok 7 - pool keeps the slots full as tasks finish\n ---\n duration_ms: 0.139511\n type: 'test'\n location: '/tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:74:1'\n failureType: 'testCodeFailure'\n error: |-\n Expected values to be strictly deep-equal:\n + actual - expected\n \n [\n + <4 empty items>\n - 'a',\n - 'b',\n - 'c',\n - 'd'\n ]\n \n code: 'ERR_ASSERTION'\n name: 'AssertionError'\n expected:\n 0: 'a'\n 1: 'b'\n 2: 'c'\n 3: 'd'\n actual:\n operator: 'deepStrictEqual'\n stack: |-\n TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-hr3gr17j/solution.test.js:85:10)\n async Test.run (node:internal/test_runner/test:1313:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:897:7)\n ...\n1..7\n# tests 7\n# suites 0\n# pass 1\n# fail 6\n# cancelled 0\n# skipped 0\n# todo 0\n# duration_ms 66.359881"
},
{
"total": 7,
"answer": "```js\nexport async function pool(thunks, concurrency) {\n const results = new Array(thunks.length);\n let index = 0;\n const running = new Set();\n\n const next = () => {\n while (index < thunks.length && running.size < concurrency) {\n const thunk = thunks[index++];\n running.add(thunk);\n thunk()\n .then((value) => {\n results[index - 1] = value;\n running.delete(thunk);\n next();\n })\n .catch((err) => {\n results[index - 1] = err;\n running.delete(thunk);\n next();\n });\n }\n };\n\n next();\n return results;\n}\n```",
"passed": 1,
"test_error": "f thunks\nnot ok 5 - concurrency larger than number of thunks\n ---\n duration_ms: 0.138022\n type: 'test'\n location: '/tmp/llmlab-js-09-pool-_r_ix29h/solution.test.js:51:1'\n failureType: 'testCodeFailure'\n error: |-\n Expected values to be strictly deep-equal:\n + actual - expected\n \n [\n + <2 empty items>\n - 1,\n - 2\n ]\n \n code: 'ERR_ASSERTION'\n name: 'AssertionError'\n expected:\n 0: 1\n 1: 2\n actual:\n operator: 'deepStrictEqual'\n stack: |-\n TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-_r_ix29h/solution.test.js:55:10)\n async Test.run (node:internal/test_runner/test:1313:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:897:7)\n ...\n# Subtest: all thunks actually run exactly once\nnot ok 6 - all thunks actually run exactly once\n ---\n duration_ms: 0.174471\n type: 'test'\n location: '/tmp/llmlab-js-09-pool-_r_ix29h/solution.test.js:59:1'\n failureType: 'testCodeFailure'\n error: |-\n Expected values to be strictly deep-equal:\n + actual - expected\n \n [\n + <6 empty items>\n - 0,\n - 1,\n - 4,\n - 9,\n - 16,\n - 25\n ]\n \n code: 'ERR_ASSERTION'\n name: 'AssertionError'\n expected:\n 0: 0\n 1: 1\n 2: 4\n 3: 9\n 4: 16\n 5: 25\n actual:\n operator: 'deepStrictEqual'\n stack: |-\n TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-_r_ix29h/solution.test.js:70:10)\n async Test.run (node:internal/test_runner/test:1313:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:897:7)\n ...\n# Subtest: pool keeps the slots full as tasks finish\nnot ok 7 - pool keeps the slots full as tasks finish\n ---\n duration_ms: 0.134821\n type: 'test'\n location: '/tmp/llmlab-js-09-pool-_r_ix29h/solution.test.js:74:1'\n failureType: 'testCodeFailure'\n error: |-\n Expected values to be strictly deep-equal:\n + actual - expected\n \n [\n + <4 empty items>\n - 'a',\n - 'b',\n - 'c',\n - 'd'\n ]\n \n code: 'ERR_ASSERTION'\n name: 'AssertionError'\n expected:\n 0: 'a'\n 1: 'b'\n 2: 'c'\n 3: 'd'\n actual:\n operator: 'deepStrictEqual'\n stack: |-\n TestContext.<anonymous> (file:///tmp/llmlab-js-09-pool-_r_ix29h/solution.test.js:85:10)\n async Test.run (node:internal/test_runner/test:1313:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:897:7)\n ...\n1..7\n# tests 7\n# suites 0\n# pass 1\n# fail 6\n# cancelled 0\n# skipped 0\n# todo 0\n# duration_ms 73.425386"
},
{
"total": 7,
"answer": "```js\nexport async function pool(thunks, concurrency) {\n const results = new Array(thunks.length);\n let index = 0;\n const running = new Set();\n\n const next = () => {\n while (index < thunks.length && running.size < concurrency) {\n const thunk = thunks[index++];\n running.add(thunk);\n thunk()\n .then((value) => {\n results[index - 1] = value;\n running.delete(thunk);\n next();\n })\n .catch((err) => {\n results[index - 1] = err;\n running.delete(thunk);\n next();\n });\n }\n };\n\n next();\n return results;\n}\n```",
"passed": 1,
"test_error": ""
}
]