Skip to content

Instantly share code, notes, and snippets.

@gregkorossy
Last active September 15, 2023 08:08
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save gregkorossy/e33be1f201cf242197d9c4d0a1fa7335 to your computer and use it in GitHub Desktop.
Save gregkorossy/e33be1f201cf242197d9c4d0a1fa7335 to your computer and use it in GitHub Desktop.
A simple implementation of a semaphore in JS
function Semaphore(max) {
var counter = 0;
var waiting = [];
var take = function() {
if (waiting.length > 0 && counter < max){
counter++;
let promise = waiting.shift();
promise.resolve();
}
}
this.acquire = function() {
if(counter < max) {
counter++
return new Promise(resolve => {
resolve();
});
} else {
return new Promise((resolve, err) => {
waiting.push({resolve: resolve, err: err});
});
}
}
this.release = function() {
counter--;
take();
}
this.purge = function() {
let unresolved = waiting.length;
for (let i = 0; i < unresolved; i++) {
waiting[i].err('Task has been purged.');
}
counter = 0;
waiting = [];
return unresolved;
}
}
// testing the semaphore
let sema = new Semaphore(2);
async function test(id) {
console.log('queueing task', id);
try {
await sema.acquire();
console.log('running task', id);
setTimeout(() => {
sema.release();
}, 2000);
} catch (e) {
console.error(id, e);
}
}
test(1);
test(2);
test(3);
test(4);
test(5);
setTimeout(() => {
test(10);
test(11);
test(12);
}, 1500);
setTimeout(() => {
test(20);
test(21);
test(22);
}, 2700);
// PURGE TEST
// setTimeout(() => {sema.purge();}, 2200);
@axos88
Copy link

axos88 commented Jan 29, 2020

  this.with = async function(cb) {
    await this.acquire();

    try {
      return await cb();
    } finally {
      await this.release();
    }
  }

@Jhappy77
Copy link

Class version for TypeScript:

export class Semaphore {
  private counter = 0;
  private waiting: { resolve: (value: unknown) => void; err: (reason?: any) => void }[] = [];
  private max: number;

  constructor(max: number) {
    this.max = max;
  }

  public take(): void {
    if (this.waiting.length > 0 && this.counter < this.max) {
      this.counter += 1;
      const promise = this.waiting.shift();
      if (promise) {
        promise.resolve(undefined);
      }
    }
  }

  public acquire(): Promise<unknown> {
    if (this.counter < this.max) {
      this.counter += 1;
      return new Promise(resolve => {
        resolve(undefined);
      });
    }
    return new Promise((resolve, err) => {
      this.waiting.push({ resolve, err });
    });
  }

  public release(): void {
    this.counter -= 1;
    this.take();
  }

  public purge(): number {
    const unresolved = this.waiting.length;

    for (let i = 0; i < unresolved; i += 1) {
      this.waiting[i].err(`Task has been purged.`);
    }

    this.counter = 0;
    this.waiting = [];

    return unresolved;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment