parallel-transform review
parallel-transform 1.2.0 is a callback-based Node Transform stream that keeps several async operations in flight while preserving input order by default. Set `ordered: false` to emit completed results immediately. It uses object mode unless disabled and treats a successful `null` or `undefined` result as a filtered item. This is concurrency on the event loop, not CPU execution across threads. Release 1.2.0 only upgrades `cyclist` to 1.0.1 so every dependency carries proper license metadata; the stream API itself did not change.
parallel-transform 1.2.0 installed in 0.7 seconds and occupied 1 MB with 0 audit findings in our sandbox, but it ships 2019-era callback and `readable-stream` 2 semantics. Keep it for proven ordered I/O streams; use a promise-native concurrency tool or current custom Transform for new code.
We installed it
| Install | ✓ · 0.7s | 10 packages on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | n/a | could not be bundled for the browser (Node-only code, most likely) |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does parallel-transform install cleanly?
Yes. In a fresh container with an empty cache, npm install parallel-transform finished in 0.7s, leaving 10 packages and 1 MB on disk. npm audit reported no known vulnerabilities.
Can parallel-transform run in a browser?
Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.
Does parallel-transform work with both ESM and CommonJS?
Yes. Both import 'parallel-transform' and require('parallel-transform') worked in Node 22 in our run. The package is published as CommonJS.
Does parallel-transform include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
parallel-transform or through2-concurrent: which should you use?
through2-concurrent: Use it for a similar callback-based concurrent transform with through2 conventions. parallel-transform 1.2.0 installed in 0.7 seconds and occupied 1 MB with 0 audit findings in our sandbox, but it ships 2019-era callback and readable-stream 2 semantics.
When should you not use parallel-transform?
The work is CPU-bound; all callbacks still run on one Node event loop and no worker thread is created
Use it if
- A maintained legacy pipeline already speaks callback-style Node streams
- Each input starts independent I/O and a fixed concurrency ceiling protects the downstream service
- Output must remain in source order even when later operations finish sooner
- Returning `null` as a successful result is a useful object-stream filter
- The work is CPU-bound; all callbacks still run on one Node event loop and no worker thread is created
- Your workers are `async` functions; returned promises are ignored unless adapted to the callback
- One slow item must not hold later results in memory; ordered mode necessarily has head-of-line blocking
- Pipeline cancellation must stop active requests or timers; the package's custom destroy method only ignores their late callbacks
- You require current stream internals, types, or active releases; 1.2.0 dates to September 2019 and depends on `readable-stream` 2
Setup reality
Our parallel-transform 1.2.0 install completed in 0.7 seconds in a fresh Node 22 container. It left 10 packages using 1 MB on disk, and npm audit reported 0 known vulnerabilities. The package measured 20 KB unpacked with 3 direct dependencies and 0 peer dependencies. require() and ESM import both loaded the CommonJS entry, which has no exports map. We found no TypeScript declarations.
Pass a positive integer concurrency and an error-first callback. The constructor does not validate that limit, so 0, a fraction, or a nonnumber can break its scheduling. Object mode defaults on. If highWaterMark is absent, the implementation uses at least 16 and raises it to the concurrency value when that is larger, which can increase upstream buffering along with active work.
Every worker must call its callback once. A returned promise is invisible, a missing callback stalls the stream, and a second callback corrupts completion accounting. A callback error emits error, ends readable output, and closes the transform. Use stream.pipeline() or install listeners. Successful null and undefined results are consumed as filters, so neither value can pass through as object data.
Ordered mode stores finished work until the next input position completes. That guarantees order but lets one slow item retain many later results. Unordered mode reduces that wait and gives nondeterministic output. Destroying the stream does not abort HTTP calls, timers, or file operations that workers already started; own AbortControllers if cancellation matters. Our esbuild browser bundle failed on Node stream code, so this belongs in Node pipelines rather than Web Streams or browser code.
Patterns
Run 8 lookups and keep source order ordered-io
const parallel = require('parallel-transform');
const enrich = parallel(8, (record, callback) => {
lookupOwner(record.ownerId, (error, owner) => {
if (error) return callback(error);
callback(null, { ...record, owner });
});
});Eight operations can be active, but one slow early record delays emission of every completed record behind it.
Emit whichever job finishes next completion-order
const transform = parallel(8, { ordered: false }, (job, callback) => {
runJob(job, callback);
});Completion order is nondeterministic. Do not join these outputs back to inputs by array position.
Transform Buffer chunks binary-mode
const transform = parallel(4, { objectMode: false }, (chunk, callback) => {
processChunk(chunk, callback);
});
fs.createReadStream('input.bin')
.pipe(transform)
.pipe(fs.createWriteStream('output.bin'));A stream chunk is an arbitrary Buffer boundary. Many compression and parsing formats require shared state across chunks.
Catch a worker failure at the pipeline pipeline-errors
const { pipeline } = require('node:stream');
pipeline(source, transform, destination, (error) => {
if (error) {
console.error('pipeline failed:', error);
process.exitCode = 1;
}
});`pipeline` observes the emitted error. Work already started by other callbacks continues unless your application cancels it.
Drop inactive objects filter-items
const activeOnly = parallel(4, (record, callback) => {
if (!record.active) return callback(null, null);
callback(null, record);
});A successful `null` or `undefined` disappears from the output instead of becoming an object-mode chunk.
Bridge an async worker to one callback promise-adapter
const transform = parallel(6, (item, callback) => {
Promise.resolve()
.then(() => enrich(item))
.then((value) => callback(null, value), callback);
});Returning the promise is insufficient. Both fulfillment and rejection must reach the callback exactly once.
Use concurrency 1 through the short form serial-form
const normalize = parallel((row, callback) => {
callback(null, { ...row, name: row.name.trim() });
});When the first argument is the worker, version 1.2.0 chooses concurrency 1 and still enables object mode.
Set activity and queue limits separately buffer-policy
const transform = parallel(4, {
objectMode: true,
highWaterMark: 8,
}, processRecord);Four is the worker ceiling and 8 is the stream buffer threshold. The package otherwise defaults the latter to at least 16.
Reject a broken worker count validate-limit
const concurrency = Number(process.env.WORKERS ?? 4);
if (!Number.isInteger(concurrency) || concurrency < 1) {
throw new TypeError('WORKERS must be a positive integer');
}
const transform = parallel(concurrency, worker);The constructor does not guard `maxParallel`; a zero or invalid value can leave writes waiting indefinitely.
Cancel fetches when the stream closes abort-requests
const active = new Set();
const transform = parallel(5, (item, callback) => {
const controller = new AbortController();
active.add(controller);
fetch(item.url, { signal: controller.signal })
.then((r) => r.text())
.then((value) => callback(null, value), callback)
.finally(() => active.delete(controller));
});
transform.on('close', () => {
for (const controller of active) controller.abort();
});Cancellation is application-owned in 1.2.0. Ensure an abort rejection cannot cause a second callback.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| through2-concurrent | npm | Use it for a similar callback-based concurrent transform with through2 conventions. |
| concurrent-transform | npm | Use it when another small object-stream concurrency helper better matches an existing pipeline. |
| p-map | npm | Use it when inputs fit an iterable and workers should be promise-native async functions. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

