interface Job { key?: string | number; running: boolean; start?: () => void; } export class Queue { readonly #concurrent: number; readonly #jobs: Job[] = []; #running = 0; constructor(concurrent = 1) { this.#concurrent = concurrent; } public async execute(func: () => Promise, key?: string | number): Promise { const job: Job = {key, running: false}; this.#jobs.push(job); // Minor optimization/workaround: various tests like the idea that a job that is immediately runnable is run without an event loop spin. // This also helps with stack traces in some cases, so avoid an `await` if we can help it. if (this.#getNext() !== job) { await new Promise((resolve): void => { job.start = (): void => { job.running = true; this.#running += 1; resolve(); }; this.#executeNext(); }); } else { job.running = true; this.#running += 1; } try { return await func(); } finally { this.#jobs.splice(this.#jobs.indexOf(job), 1); this.#running = Math.max(this.#running - 1, 0); this.#executeNext(); } } #executeNext(): void { const job = this.#getNext(); if (job) { // biome-ignore lint/style/noNonNullAssertion: if we get here, start is always defined for job job.start!(); } } #getNext(): Job | undefined { if (this.#running > this.#concurrent - 1) { return undefined; } for (let i = 0; i < this.#jobs.length; i++) { const job = this.#jobs[i]; if (!job.running && (!job.key || !this.#jobs.find((j) => j.key === job.key && j.running))) { return job; } } return undefined; } public clear(): void { this.#running = 0; this.#jobs.length = 0; } public count(): number { return this.#jobs.length; } }