01 · Problem
What is it for?
A thread per task wastes startup resources; an unbounded task queue risks memory exhaustion.
02 · Mental model
How should you think about it?
The main thread owns task promises and assignment. A worker executes one request and replies with an explicit success or error.
Executes trusted self-contained functions on bounded persistent workers with timeout recovery. Function source cannot capture closures and must never be untrusted. A preinstalled handler registry and transferables are future extensions.
03 · Control flow
What happens in which order?
- Start workers once.
- Queue task identity and serializable input.
- Assign one task per idle worker.
- Settle its promise on result or error.
- Release or replace the worker.
- Reject pending work on shutdown.
04 · Building blocks
The important contracts
WorkerPool.executeMethodEnqueue a trusted self-contained function and arguments.WorkerPoolOptionsSet queue capacity and task timeout.destroyReject pending promises and await worker termination.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Start workers once.
- Queue task identity and serializable input.
- Assign one task per idle worker.
- Settle its promise on result or error.
- Release or replace the worker.
- Reject pending work on shutdown.
// worker.ts
const handlers = {
square: (value: number) => value * value,
};
parentPort?.on("message", async ({ id, task, payload }) => {
try {
const result = await handlers[task as keyof typeof handlers](payload);
parentPort?.postMessage({ id, ok: true, result });
} catch (error) {
parentPort?.postMessage({ id, ok: false, error: String(error) });
}
});06 · Verify
What should you test?
- Resolve 0, false, empty string and undefined.
- Reject thrown errors and crashed workers.
- Reject queued and active work during shutdown.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
Resolve 0, false, empty string and undefined.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- Serialized functions cannot capture closures.
- Never execute untrusted function source.
- A handler registry is a stronger extension for reusable applications.
Function source cannot capture closures and must never be untrusted. A preinstalled handler registry and transferables are future extensions.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/worker-pool/examples/basic.ts