Initial
This commit is contained in:
113
resources/app/common/utils/semaphore.mjs
Normal file
113
resources/app/common/utils/semaphore.mjs
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* A simple Semaphore implementation which provides a limited queue for ensuring proper concurrency.
|
||||
* @param {number} [max=1] The maximum number of tasks which are allowed concurrently.
|
||||
*
|
||||
* @example Using a Semaphore
|
||||
* ```js
|
||||
* // Some async function that takes time to execute
|
||||
* function fn(x) {
|
||||
* return new Promise(resolve => {
|
||||
* setTimeout(() => {
|
||||
* console.log(x);
|
||||
* resolve(x);
|
||||
* }, 1000));
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // Create a Semaphore and add many concurrent tasks
|
||||
* const semaphore = new Semaphore(1);
|
||||
* for ( let i of Array.fromRange(100) ) {
|
||||
* semaphore.add(fn, i);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class Semaphore {
|
||||
constructor(max=1) {
|
||||
|
||||
/**
|
||||
* The maximum number of tasks which can be simultaneously attempted.
|
||||
* @type {number}
|
||||
*/
|
||||
this.max = max;
|
||||
|
||||
/**
|
||||
* A queue of pending function signatures
|
||||
* @type {Array<Array<Function|*>>}
|
||||
* @private
|
||||
*/
|
||||
this._queue = [];
|
||||
|
||||
/**
|
||||
* The number of tasks which are currently underway
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this._active = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of pending tasks remaining in the queue
|
||||
* @type {number}
|
||||
*/
|
||||
get remaining() {
|
||||
return this._queue.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of actively executing tasks
|
||||
* @type {number}
|
||||
*/
|
||||
get active() {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new tasks to the managed queue
|
||||
* @param {Function} fn A callable function
|
||||
* @param {...*} [args] Function arguments
|
||||
* @returns {Promise} A promise that resolves once the added function is executed
|
||||
*/
|
||||
add(fn, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._queue.push([fn, args, resolve, reject]);
|
||||
return this._try();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Abandon any tasks which have not yet concluded
|
||||
*/
|
||||
clear() {
|
||||
this._queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to perform a task from the queue.
|
||||
* If all workers are busy, do nothing.
|
||||
* If successful, try again.
|
||||
* @private
|
||||
*/
|
||||
async _try() {
|
||||
if ( (this.active === this.max) || !this.remaining ) return false;
|
||||
|
||||
// Obtain the next task from the queue
|
||||
const next = this._queue.shift();
|
||||
if ( !next ) return;
|
||||
this._active += 1;
|
||||
|
||||
// Try and execute it, resolving its promise
|
||||
const [fn, args, resolve, reject] = next;
|
||||
try {
|
||||
const r = await fn(...args);
|
||||
resolve(r);
|
||||
}
|
||||
catch(err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
// Try the next function in the queue
|
||||
this._active -= 1;
|
||||
return this._try();
|
||||
}
|
||||
}
|
||||
export default Semaphore;
|
||||
Reference in New Issue
Block a user