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.SchedulerOptions
interface 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;
}
PriorityQueueFifo
new 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
).