mrkeyoor.com_
Sat 08 Aug 22:55 UTC
npmUtilsupdated 08 Aug 2026

qjobs

qjobs is an in-memory callback queue for Node.js. You add functions plus an argument object or array, choose a maximum concurrency, attach EventEmitter listeners, and call run. Each function must call next to release its slot. The queue can pause launches, abort pending work, report counters, accept work while already running, and insert a delay between full batches. It does not persist jobs, retry failures, return promises, coordinate multiple processes, or provide workers of its own.

Verdict

qjobs can remain in stable callback-era code, but its missing error channel and call-next discipline make it a poor new dependency. Use p-queue for promises or fastq for a focused in-memory worker queue.

API stability3/5The constructor, add, run, pause, abort, stats, setConcurrency, setInterval, and event names have not changed since the last 1.2.0 release in February 2018. That makes existing integrations predictable, but the contract is loose: args is mutated, next must be called exactly once, promise returns are ignored, errors have no channel, and open issue #6 describes a restart edge case. A frozen API is not the same as a carefully specified one.
Docs2/5The README lists the main features and provides one complete example covering construction, adding array arguments, lifecycle events, pausing from jobEnd, unpausing, and abort. It does not provide a method reference, define event payloads, explain that args is mandatory and mutated with _jobId, distinguish interval batching from rate limiting, or state what happens when a job throws, rejects, omits next, or calls next twice. Tests and source are required for operationally important details.
Maintenance1/5The repository is not archived and npm does not mark qjobs deprecated, but the last push and 1.2.0 release were in February 2018. The three open issues cover substantive behavior: no job-error event, completed queues not noticing later additions, and the requirement that args be an object. The project has no recent CI evidence, dependency updates, typing work, or adaptation to promise-based Node conventions, so users should assume they own future fixes.
Ecosystem2/5qjobs recorded 3,219,900 downloads for the measured week, but the repository has 19 stars and only three forks, suggesting much of that traffic is transitive rather than an active user community. It uses Node's standard EventEmitter and has no runtime dependencies, which makes integration simple. There are no adapters, plugins, worker backends, TypeScript declarations, framework guides, or persistence integrations, and modern queue packages cover a much broader set of production needs.

Use it if

  • You are maintaining callback-era Node code that already uses qjobs events and next callbacks
  • All work lives in one process and losing pending jobs on restart is acceptable
  • You need a small concurrency cap plus pause, abort, progress counters, and lifecycle events
  • You want to add jobs while a run is active and can enforce a strict call-next-once convention
Skip it if

Setup reality

npm install qjobs is the whole install. Version 1.2.0 has no runtime dependencies, peers, native compilation, configuration file, or external service, and its engine declaration permits Node 0.9 and newer. That age is also the warning: the only entry is CommonJS, the API is callback-based, and there are no TypeScript declarations. Construct with new QJobs({ maxConcurrency, interval }), add each job as queue.add(fn, args), register listeners before starting, and call queue.run(). args is not optional in practice. The run method writes an internal _jobId property onto it before calling the job, so passing undefined throws and passing a frozen object fails; your own object or array is mutated. Every job must call next exactly once. Forgetting it permanently occupies a slot, calling it twice corrupts running and done counters, throwing escapes from a setTimeout callback, and returning or rejecting a promise has no effect on queue progress. pause(true) prevents new launches but does not interrupt running jobs, and it starts a one-second timer that emits pause repeatedly until pause(false). abort() only flips a flag; pending entries are discarded on a later run cycle while jobs already executing continue. interval is not a per-job rate limit. It waits between batches after the concurrency ceiling has been filled and running work drains, emitting the misspelled continu event when it resumes. The source's boundary logic is particularly risky with unusual concurrency values, so do not treat it as an API quota scheduler. stats() exposes underscore-prefixed counters and may report NaN progress before any jobs are added. Finally, dynamic additions while work is active are supported, but adding after end does not automatically restart the queue. Call run again and expect another start event because jobsDone can also affect that event logic.

Patterns

Create a concurrency-limited queuecreate-queue

const QJobs = require('qjobs');
const queue = new QJobs({ maxConcurrency: 4 });

The default concurrency is 10 when maxConcurrency is omitted or otherwise falsy.

Add a callback jobadd-callback-job

queue.add(function fetchOne(args, next) {
  fetchRecord(args.id, function (error, record) {
    args.error = error || null;
    args.record = record;
    next();
  });
}, { id: 42 });

qjobs mutates the args value by adding _jobId, and the job must call next exactly once even on failure.

Attach listeners and start workstart-queue

queue.on('start', () => console.log('queue started'));
queue.on('end', () => console.log('queue drained'));
queue.run();

Register listeners before run because start is emitted synchronously at the beginning of the call.

Observe individual job transitionsobserve-jobs

queue.on('jobStart', (args) => {
  console.log('starting', args._jobId, args.id);
});
queue.on('jobEnd', (args) => {
  console.log('finished', args._jobId, args.id);
});

The internal numeric _jobId is written onto the same args object or array supplied to add.

Adapt a promise task safelywrap-promise-job

queue.add(function runAsync(args, next) {
  Promise.resolve()
    .then(() => processItem(args.item))
    .then(
      (value) => { args.value = value; next(); },
      (error) => { args.error = error; next(); }
    );
}, { item });

Returning the promise is not enough; qjobs does not observe it, so both settlement branches must call next.

Pause and resume pending launchespause-launches

queue.pause(true);

setTimeout(() => {
  queue.pause(false);
}, 5000);

Pausing does not cancel jobs already running, and the queue emits pause once per second until resumed.

Track how long the queue is pausedreport-pause-time

queue.on('pause', (milliseconds) => {
  console.log('paused for', milliseconds, 'ms');
});
queue.on('unpause', () => console.log('resumed'));

pause is a repeated timer event, not a one-time state-change event; unpause fires once when the timer is cleared.

Poll queue statisticsread-progress

const timer = setInterval(() => {
  const stats = queue.stats();
  console.log(stats._status, stats._progress + '%', stats._jobsRunning);
  if (stats._status === 'Finished') clearInterval(timer);
}, 1000);

The returned field names begin with underscores, and progress can be NaN when the queue has no jobs.

Change the launch limitchange-concurrency

queue.setConcurrency(8);

Changing the value does not preempt running jobs or validate the number; use a positive integer and test interval behavior.

Delay between concurrency batchesdelay-between-batches

const queue = new QJobs({
  maxConcurrency: 5,
  interval: 1000,
});
queue.on('sleep', () => console.log('batch drained, waiting'));
queue.on('continu', () => console.log('starting next batch'));

The event is spelled continu in version 1.2.0, and interval is batch spacing rather than a strict per-time-window rate limit.

Discard work that has not startedabort-pending-jobs

queue.abort();

abort sets a flag and clears pending jobs on a later run cycle; already running jobs continue and still must call next.

Run jobs added after the queue endedrestart-after-end

queue.on('end', () => {
  // Later, when more work arrives:
});

function enqueueLater(item) {
  queue.add(worker, { item });
  queue.run();
}

A completed queue does not automatically notice later additions, as tracked in open issue #6; explicitly call run again.

Alternatives

PackageRegistryPick it when
p-queuenpmChoose it for modern promise tasks, concurrency and interval caps, priorities, timeouts, and idle promises
fastqnpmChoose it for a fast in-memory worker queue with callback and promise interfaces plus drain and error hooks
asyncnpmChoose it when concurrency-limited queues are one of several callback and async control-flow tools you need