TIL about semaphores
POSTED ON:
TAGS: javascript advanced programming
What is a Semaphore? #
A semaphore limits access to a resource to some number of consumers. Any new requests for the resource will get stalled until later, eventually to be handled.
ELI5 version:
A semaphore is kind of like a revolving door at an office building: they make sure that only one person at a time is passing through the door way, at any given time.
With a normal door, sometimes, someone is trying to walk through, while someone else is trying to walk through in the opposite direction. This would cause what is commonly referred to as a "dead lock".
With the revolving door, you can only go through it if there is a space for you to stand in, as it spins around. You will get through the door, you may just have to wait for an available space, regardless of whether you're entering the building, or leaving.
via indienick
Use cases #
One major usecase is managing api usage. If 10,000 hit your api at the same time, that's literally a DDOS attack.
But by setting up a Semaphore, you can provide service for a large chunk, and the rest can either wait, get thrown in a queue, ignored, etc.
Code #
export default class Semaphore {
/**
* Creates a semaphore that limits the number of concurrent Promises being handled
* @param {*} maxConcurrentRequests max number of concurrent promises being handled at any time
*/
constructor(maxConcurrentRequests = 1) {
this.currentRequests = [];
this.runningRequests = 0;
this.maxConcurrentRequests = maxConcurrentRequests;
}
/**
* Returns a Promise that will eventually return the result of the function passed in
* Use this to limit the number of concurrent function executions
* @param {*} fnToCall function that has a cap on the number of concurrent executions
* @param {...any} args any arguments to be passed to fnToCall
* @returns Promise that will resolve with the resolved value as if the function passed in was directly called
*/
callFunction(fnToCall, ...args) {
return new Promise((resolve, reject) => {
this.currentRequests.push({
resolve,
reject,
fnToCall,
args,
});
this.tryNext();
});
}
tryNext() {
if (!this.currentRequests.length) {
return;
} else if (this.runningRequests < this.maxConcurrentRequests) {
let { resolve, reject, fnToCall, args } = this.currentRequests.shift();
this.runningRequests++;
let req = fnToCall(...args);
req.then((res) => resolve(res))
.catch((err) => reject(err))
.finally(() => {
this.runningRequests--;
this.tryNext();
});
}
}
}
/* HOW TO USE */
const throttler = new Semaphore(2);
throttler.callFunction(fetch, 'www.facebook.com');
throttler.callFunction(fetch, 'www.amazon.com');
throttler.callFunction(fetch, 'www.netflix.com');
throttler.callFunction(fetch, 'www.google.com');
via Weiming Wu's Semaphores in JavaScript
Related TILs
Tagged: javascript