Counting Semaphore For Async Concurrency

A counting semaphore that bounds in-flight async tasks with FIFO fairness and a safe release path for pooled resources.

JavascriptData Fetching

Code

export class Semaphore {
  constructor(max) {
    if (!Number.isInteger(max) || max < 1) throw new RangeError("max must be a positive integer");
    this.max = max;
    this.active = 0;
    this.waiters = [];
  }
  get inFlight() {
    return this.active;
  }
  async acquire() {
    if (this.active < this.max) {
      this.active += 1;
      return this.makeRelease();
    }
    await new Promise((resolve) => this.waiters.push(resolve));
    return this.makeRelease();
  }
  makeRelease() {
    let released = false;
    return () => {
      if (released) return;
      released = true;
      this.active -= 1;
      const next = this.waiters.shift();
      if (next) {
        this.active += 1;
        next();
      }
    };
  }
  async run(task) {
    const release = await this.acquire();
    try {
      return await task();
    } finally {
      release();
    }
  }
  async runAll(tasks) {
    return Promise.all(tasks.map((t) => this.run(t)));
  }
}