Skip to content

Cancellable Batch ​

Problem ​

Several worker tasks share one lifetime and should stop together when input becomes obsolete, one task fails, or the consumer stops reading.

Solution ​

Use runBatch() with an application-owned signal. Familiar starts pool work concurrently, yields results in input order, and settles cancelled siblings before iteration ends.

ts
import { createWorker, runBatch } from '@vielzeug/familiar';

const pool = createWorker<string, string>(new URL('./normalize.worker.ts', import.meta.url));
const controller = new AbortController();

try {
  for await (const value of runBatch(pool, ['a', 'b', 'c'], {
    signal: controller.signal,
    timeout: 5_000,
  })) {
    console.log(value);
  }
} finally {
  pool.dispose();
}

Provide a separate transfer list for each input when required:

ts
for await (const result of runBatch(pool, buffers, {
  getTransferables: (buffer) => [buffer],
})) {
  consume(result);
}

Pitfalls ​

  • Each transferable can be detached only once; never return one shared transfer list for multiple inputs.
  • Aborting active work terminates and lazily replaces occupied worker slots.
  • Results are yielded in input order while later tasks continue running concurrently.
  • The first failure aborts unfinished siblings and is rethrown.
  • Breaking or calling return() cancels unfinished work and waits for settlement.
  • Use Promise.allSettled() over individual pool.run() calls when failures should remain independent.