export interface ICallFn {
resolve: (value: unknown) => void;
reject: (reason?: unknown) => void;
fnToCall: (...args: any[]) => Promise<any>;
args: any[];
}
export default class Semaphore {
currentRequests: ICallFn[];
runningRequests: number;
maxConcurrentRequests: number;
/**
* 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;
}
if (this.runningRequests < this.maxConcurrentRequests) {
const { resolve, reject, fnToCall, args } = this.currentRequests.shift();
this.runningRequests++;
fnToCall(...args)
.then((res) => resolve(res))
.catch((err) => reject(err))
.finally(() => {
this.runningRequests--;
this.tryNext();
});
}
}
}