Simple Semaphores
A minimal Semaphore class in JavaScript: acquire blocks when no permits are available, release hands permits directly to waiting callers, and a run helper wraps both with try/finally.
export default class Semaphore {
constructor(maxPermits = 1) {
if (!Number.isInteger(maxPermits) || maxPermits < 1) {
throw new Error("maxPermits must be an integer >= 1");
}
this.maxPermits = maxPermits;
this.available = maxPermits;
this.waiters = [];
}
acquire() {
if (this.available > 0) {
this.available--;
return Promise.resolve();
}
return new Promise(resolve => {
this.waiters.push(resolve);
});
}
release() {
if (this.waiters.length > 0) {
// Hand permit directly to next waiter
const next = this.waiters.shift();
next();
// No need to increment available
} else {
// No waiters, return permit to pool
// this.available++; // if released is called too many times
// clamp on release to prevent permit inflation if release is called too many times.
// dont call x++ because of post-increment semantics
this.available = Math.min(this.available + 1, this.maxPermits);
}
}
// Optional helper
async run(fn, ...args) {
await this.acquire();
try {
return await fn(...args);
} finally {
this.release();
}
}
}