interface Options { concurrency: number; } type Runnable = () => Promise; export class ExecutorQueue { private queue: Array = []; private running = 0; private _concurrency: number; constructor(options?: Options) { this._concurrency = options?.concurrency || 2; } get concurrency() { return this._concurrency; } set concurrency(concurrency: number) { if (concurrency < 1) { return; } this._concurrency = concurrency; const v = concurrency - this.running; if (v > 0) { [...new Array(this._concurrency)].forEach(() => this.tryRun()); } } addTask(task: () => Promise): Promise { return new Promise((resolve, reject) => { // Add a custom task that wrap the original one; this.queue.push(async () => { try { this.running++; const result = task(); resolve(await result); } catch (e) { reject(e); } finally { this.taskFinished(); } }); // Then run it if possible ! this.tryRun(); }); } private taskFinished(): void { this.running--; this.tryRun(); } private tryRun() { if (this.running >= this.concurrency) { return; } const runnable = this.queue.shift(); if (!runnable) { return; } runnable(); } }