export class ExecutionQueueOverflowError extends Error { constructor(message = "Execution queue is full") { super(message); this.name = "ExecutionQueueOverflowError"; } } type Task = () => Promise; interface PendingTask { task: () => Promise; resolve: (value: unknown) => void; reject: (reason?: unknown) => void; } export class ExecutionQueue { private running = 0; private readonly queue: PendingTask[] = []; constructor( private readonly concurrency: number, private readonly queueLimit: number ) {} run(task: Task): Promise { if (this.running < this.concurrency) { return this.execute(task); } const limit = this.queueLimit; if (Number.isFinite(limit) && limit >= 0 && this.queue.length >= limit) { throw new ExecutionQueueOverflowError(); } return new Promise((resolve, reject) => { this.queue.push({ task: () => task(), resolve: (value) => resolve(value as T), reject, }); }); } private async execute(task: Task): Promise { this.running += 1; try { return await task(); } finally { this.running -= 1; this.processQueue(); } } private processQueue(): void { while (this.running < this.concurrency && this.queue.length > 0) { const next = this.queue.shift(); if (!next) { return; } this.execute(next.task).then(next.resolve).catch(next.reject); } } }