← run

js-09-pool

0.000
0/1 tests· concurrency
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 inFlight = 0;
  let nextIndex = 0;

  const enqueue = (thunk, index) => {
    results[index] = thunk();
    inFlight++;
  };

  const processQueue = () => {
    while (inFlight < concurrency && nextIndex < thunks.length) {
      enqueue(thunks[nextIndex++]);
    }
  };

  const cleanup = () => {
    inFlight = 0;
    processQueue();
  };

  const cleanupPromise = new Promise(cleanup);

  const promiseChain = new Promise((resolve, reject) => {
    const processNext = () => {
      if (nextIndex >= thunks.length) {
        cleanupPromise.then(resolve, reject);
        return;
      }

      processQueue();

      Promise.race(results.slice(nextIndex)).then((result) => {
        enqueue(thunks[nextIndex++], nextIndex);
        processNext();
      });
    };

    processNext();
  });

  return promiseChain;
}
```

Test output (stdout)

TAP version 13

Errors (stderr)


[TIMEOUT]
Self-repair attempts
[
  {
    "total": 1,
    "answer": "```js\nexport async function pool(thunks, concurrency) {\n  const results = new Array(thunks.length);\n  let inFlight = 0;\n  let nextIndex = 0;\n\n  const enqueue = (thunk, index) => {\n    results[index] = thunk();\n    inFlight++;\n  };\n\n  const processQueue = () => {\n    while (inFlight < concurrency && nextIndex < thunks.length) {\n      enqueue(thunks[nextIndex++]);\n    }\n  };\n\n  const cleanup = () => {\n    inFlight = 0;\n    processQueue();\n  };\n\n  const cleanupPromise = new Promise(cleanup);\n\n  const promiseChain = new Promise((resolve, reject) => {\n    const processNext = () => {\n      if (nextIndex >= thunks.length) {\n        cleanupPromise.then(resolve, reject);\n        return;\n      }\n\n      processQueue();\n\n      Promise.race(results.slice(nextIndex)).then((result) => {\n        enqueue(thunks[nextIndex++], nextIndex);\n        processNext();\n      });\n    };\n\n    processNext();\n  });\n\n  return promiseChain;\n}\n```",
    "passed": 0,
    "test_error": "TAP version 13\n\n\n[TIMEOUT]"
  },
  {
    "total": 1,
    "answer": "```js\nexport async function pool(thunks, concurrency) {\n  const results = new Array(thunks.length);\n  let inFlight = 0;\n  let nextIndex = 0;\n\n  const enqueue = (thunk, index) => {\n    results[index] = thunk();\n    inFlight++;\n  };\n\n  const processQueue = () => {\n    while (inFlight < concurrency && nextIndex < thunks.length) {\n      enqueue(thunks[nextIndex++]);\n    }\n  };\n\n  const cleanup = () => {\n    inFlight = 0;\n    nextIndex = 0;\n  };\n\n  const cleanupPromise = new Promise(cleanup);\n\n  const promiseChain = new Promise((resolve, reject) => {\n    const processNext = () => {\n      if (nextIndex >= thunks.length) {\n        cleanupPromise.then(resolve, reject);\n        return;\n      }\n\n      processQueue();\n\n      Promise.race(results.slice(nextIndex)).then((result) => {\n        enqueue(thunks[nextIndex++], nextIndex);\n        processNext();\n      });\n    };\n\n    processNext();\n  });\n\n  return promiseChain;\n}\n```",
    "passed": 0,
    "test_error": "TAP version 13\n\n\n[TIMEOUT]"
  },
  {
    "total": 1,
    "answer": "```js\nexport async function pool(thunks, concurrency) {\n  const results = new Array(thunks.length);\n  let inFlight = 0;\n  let nextIndex = 0;\n\n  const enqueue = (thunk, index) => {\n    results[index] = thunk();\n    inFlight++;\n  };\n\n  const processQueue = () => {\n    while (inFlight < concurrency && nextIndex < thunks.length) {\n      enqueue(thunks[nextIndex++]);\n    }\n  };\n\n  const cleanup = () => {\n    inFlight = 0;\n    nextIndex = 0;\n  };\n\n  const cleanupPromise = new Promise(cleanup);\n\n  const promiseChain = new Promise((resolve, reject) => {\n    const processNext = () => {\n      if (nextIndex >= thunks.length) {\n        cleanupPromise.then(resolve, reject);\n        return;\n      }\n\n      processQueue();\n\n      Promise.race(results.slice(nextIndex)).then((result) => {\n        enqueue(thunks[nextIndex++], nextIndex);\n        processNext();\n      });\n    };\n\n    processNext();\n  });\n\n  return promiseChain;\n}\n```",
    "passed": 0,
    "test_error": ""
  }
]