qjobs review
qjobs 1.2.0 is a single-process callback queue for Node. Each queued item stores a function and a mutable arguments value; the function receives those arguments plus `next`, which releases one concurrency slot. The queue can pause new launches, discard waiting work, accept additions while running, emit lifecycle events, and expose counters. Its optional interval delays the next batch after the concurrency ceiling is reached. It has no persistence, Promise awareness, retry policy, worker pool, or multi-process coordination.
Our qjobs 1.2.0 install took 0.7 seconds and left a single 1 MB package with no audit findings, but one missed `next()` can stall the queue forever. Keep it in stable callback code; choose a Promise-aware queue for new work.
We installed it
| Install | ✓ · 0.7s | 1 package 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 qjobs install cleanly?
Yes. In a fresh container with an empty cache, npm install qjobs finished in 0.7s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
Can qjobs 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 qjobs work with both ESM and CommonJS?
Yes. Both import 'qjobs' and require('qjobs') worked in Node 22 in our run. The package is published as CommonJS.
Does qjobs include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
qjobs or p-queue: which should you use?
p-queue: Use it for Promise tasks, interval caps, priorities, timeouts, and idle waiting. Our qjobs 1.2.0 install took 0.7 seconds and left a single 1 MB package with no audit findings, but one missed next() can stall the queue forever.
When should you not use qjobs?
Jobs are async functions: qjobs ignores returned Promises and waits only for its callback
Use it if
- You maintain callback-era code already wired to qjobs events and the mandatory `next()` convention
- Pending work may disappear on process exit because the queue is strictly in memory
- A concurrency cap, pause switch, progress counters, and dynamic additions are enough
- Each job can own its errors and call `next()` exactly once on every completion path
- Jobs are async functions: qjobs ignores returned Promises and waits only for its callback
- You need an error channel or retries: source code neither catches thrown jobs nor accepts `next(error)`
- Work must survive restarts or run across processes: all pending jobs and counters live in one JavaScript object
- You need a real rate limiter: `interval` sleeps between drained concurrency batches rather than enforcing a time-window quota
- You require maintained types and packaging: 1.2.0 has no declarations or exports map, and the repository stopped pushing in 2018
Setup reality
We installed qjobs 1.2.0 in our sandbox in 0.7 seconds. It left 1 package and 1 MB on disk; npm audit found 0 known vulnerabilities. The package is 64 KB unpacked, has 0 direct dependencies and 0 peer dependencies, uses MIT, and declares Node 0.9 or newer. There are no credentials, native builds, config files, or external services.
The package is CommonJS and has no exports map. require() and ESM import worked in Node 22, while no TypeScript declarations were present. Our esbuild browser build failed, which fits a module based on Node's EventEmitter. Construct the queue, register listeners before run(), and pass an object or array as every job's args because the source writes _jobId onto it.
Every job must invoke next() exactly once. Omitting it permanently consumes a concurrency slot; calling twice drives counters out of sync. A thrown error escapes from the timer callback, and a rejected Promise is invisible unless your adapter catches it and calls next(). pause(true) stops new starts and emits a pause duration once per second, though jobs already running continue.
abort() sets a flag, then pending entries are cleared on a later run cycle; active jobs still finish. The interval option waits after a full batch drains and emits the misspelled continu event when work resumes. It is not a per-job delay. Jobs added after an end event do not automatically restart, so call run() again. stats() property names begin with _ and can calculate an invalid percentage before jobs exist.
Patterns
Set a four-job concurrency ceiling create-queue
const QJobs = require('qjobs');
const queue = new QJobs({ maxConcurrency: 4 });The constructor falls back to a concurrency of 10 when the supplied value is falsy.
Add one callback job add-job
queue.add(function (args, next) {
fetchRecord(args.id, (error, record) => {
args.error = error || null;
args.record = record;
next();
});
}, { id: 42 });qjobs adds `_jobId` to the args object. Call `next()` exactly once on both success and failure paths.
Listen before starting work start-queue
queue.on('start', () => console.log('started'));
queue.on('end', () => console.log('drained'));
queue.run();`start` is emitted synchronously inside `run()`, so listeners registered afterward miss it.
Log individual starts and finishes observe-jobs
queue.on('jobStart', args => console.log('start', args._jobId));
queue.on('jobEnd', args => console.log('end', args._jobId));Both events receive the same mutable args value supplied to `add()`.
Bridge a Promise task to next adapt-promise
queue.add(function (args, next) {
processItem(args.item).then(
value => { args.value = value; next(); },
error => { args.error = error; next(); }
);
}, { item });Returning the Promise has no effect on qjobs. Both settlement branches must release the slot.
Pause pending launches for five seconds pause-queue
queue.pause(true);
setTimeout(() => queue.pause(false), 5000);Running jobs continue. While paused, a timer emits the `pause` event every 1 second.
Poll queue progress read-stats
const state = queue.stats();
console.log(state._status, state._progress, state._jobsRunning);Every field name begins with `_`, and `_progress` can be NaN when no jobs have been added.
Wait between full concurrency batches delay-batches
const queue = new QJobs({ maxConcurrency: 5, interval: 1000 });
queue.on('sleep', () => console.log('waiting'));
queue.on('continu', () => console.log('resuming'));The event name is `continu`. A 1000 ms interval does not impose a five-jobs-per-second quota.
Discard work that has not started abort-pending
queue.abort();Active jobs are not cancelled and still have to call `next()`. Pending entries clear during a subsequent run cycle.
Run newly added work after a drain restart-after-end
function enqueueLater(item) {
queue.add(worker, { item });
queue.run();
}A completed queue does not automatically start jobs added later. Call `run()` after the addition.
Alternatives
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.

