A priority request scheduler runs at most concurrency async tasks at once and, whenever a slot frees, starts the highest-priority task waiting in line — not the one that has waited longest. It is what keeps a user-blocking fetch ahead of background prefetches when the browser or your API can only handle so many requests in parallel: you cap how many run together, and you make sure the critical one goes next.
Implement priorityRequestScheduler({ concurrency }). It returns a scheduler object. Calling schedule(task, priority) enqueues an async (or sync) task function and returns a promise that settles with task()'s result or rejection. A larger priority number is more urgent, and the default is 0. At most concurrency tasks run at once; the rest wait. When a running task settles, the freed slot goes to the highest-priority waiter, with ties broken in the order tasks were scheduled (FIFO). A task that rejects frees its slot like any other and never affects the others.
type Task<T> = () => Promise<T> | T;
interface Scheduler {
// Enqueue a task; settles with its result or rejection. Higher priority runs first.
schedule<T>(task: Task<T>, priority?: number): Promise<T>;
readonly size: number; // tasks waiting for a slot
readonly pending: number; // tasks running right now
}
function priorityRequestScheduler(options: { concurrency?: number }): Scheduler;
const scheduler = priorityRequestScheduler({ concurrency: 1 });
// One lane, and it is busy running `render`.
scheduler.schedule(render, 0);
// Three more arrive while the lane is full.
scheduler.schedule(prefetchA, 0); // background
scheduler.schedule(loadProfile, 10); // critical — higher priority
scheduler.schedule(prefetchB, 0); // background
// When `render` finishes, the freed lane goes to loadProfile (priority 10),
// NOT prefetchA — even though prefetchA was queued first.
const scheduler = priorityRequestScheduler({ concurrency: 2 });
const body = await scheduler.schedule(async () => {
const res = await fetch('/api/user');
return res.json();
});
// A task that rejects settles only its own promise; the slot is freed and the
// next waiter runs. Other tasks are untouched.
scheduler.schedule(async () => {
throw new Error('offline');
}).catch(() => {});
priority is a number and a larger value is more urgent. When omitted it defaults to 0.concurrency is a positive integer — you do not need to validate it, change it later, or support cancellation.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.