A priority‑first FIFO scheduler for asynchronous work with bounded concurrency, pause/resume, idle detection, and backpressure. Ideal for task orchestration in CLIs, servers, and background workers.
maxConcurrency.maxQueueSize rejects add() when the queue is full.onIdle() resolves when nothing is running or queued.Environment: Node.js ≥ 18.
# npm
npm install priority-scheduler-queue
# pnpm
pnpm add priority-scheduler-queue
# yarn
yarn add priority-scheduler-queue
import { PriorityQueueFifo } from 'priority-scheduler-queue';
const queue = new PriorityQueueFifo({ maxConcurrency: 2 });
const work = (id: number) => new Promise<string>((res) => setTimeout(() => res(`done ${id}`), 200));
// Add tasks with optional priorities (default 0)
const p1 = queue.add(() => work(1), { priority: 0 });
const p2 = queue.add(() => work(2), { priority: 5 }); // runs before p1
const p3 = queue.add(() => work(3));
await Promise.all([p1, p2, p3]);
await queue.onIdle();
maxConcurrency tasks at once.maxQueueSize is reached, add() immediately rejects.SchedulerOptionsinterface SchedulerOptions {
/** Max number of tasks allowed to run at the same time. Default: os.cpus().length */
maxConcurrency?: number;
/** Default task priority. Higher = sooner. Default: 0 */
defaultPriority?: number;
/** Max number of tasks allowed in the queue before add() rejects. Default: Infinity */
maxQueueSize?: number;
}
PriorityQueueFifonew PriorityQueueFifo(options?: SchedulerOptions)
add<T>(taskFn: () => Promise<T>, opts?: { priority?: number; id?: string }): Promise<T>
pause(): void / resume(): void
onIdle(): Promise<void>
clear(finalError?: unknown): void
size: number
runningCount: number
maxConcurrency).isCurrentlyPaused: boolean
Use integers. Suggested convention:
10 — high priority0 — normal-10 — low priorityqueue.add(() => doWork('low'), { priority: -10 });
queue.add(() => doWork('normal')); // 0
queue.add(() => doWork('high'), { priority: 10 });
High priority starts first. Within the same priority, earlier add() calls start earlier.
queue.pause();
queue.add(() => fetchExpensive()); // queued but not started
// ...later
queue.resume(); // starts queued tasks up to maxConcurrency
await queue.onIdle(); // resolves when nothing is running or queued
queue.clear();
// All not-yet-started tasks are rejected with an Error.
You can pass a custom error:
queue.clear(new Error('Shutting down'));
const q = new PriorityQueueFifo({ maxQueueSize: 100 });
try {
await q.add(() => doWork());
} catch (e) {
// If size >= 100 at the time of add(), you land here.
}
Retries are intentionally not built-in; compose them as needed:
async function withRetry<T>(fn: () => Promise<T>, retries = 2) {
let lastErr: unknown;
for (let i = 0; i <= retries; i++) {
try {
return await fn();
} catch (e) {
lastErr = e;
}
}
throw lastErr;
}
queue.add(() => withRetry(() => flakyFetch()));
add() resolves/rejects with your task’s result/error.add() rejects immediately.queue.add(doThing).then(handle, handleError);
maxConcurrency thoughtfully. For CPU-bound tasks, matching os.cpus().length is a good default.Q: Does it run in the browser?
A: It targets Node.js (uses os.cpus()). Browser builds would need a polyfill for os or a custom maxConcurrency.
Q: What if my tasks aren’t promises?
A: Wrap them: queue.add(async () => doSyncThing()).
Q: Can I reorder tasks after enqueueing?
A: No—use priorities at add() time.
Q: Is cancellation supported?
A: clear() cancels queued jobs. For running tasks, use your own cancellation (e.g., AbortController within taskFn).