enqueu
Version:
Promise queue for concurrency control
197 lines (164 loc) • 4.39 kB
text/typescript
type Queu = {
fn: Function;
arguments: any;
priority: number;
resolve: Function;
reject: Function;
started?: boolean;
};
type Options = Partial<{
priority: number;
}>;
type OptionsConstructor = Partial<{
maxSize: number;
maxConcurrency: number;
}>;
class Enqueu {
private _queu: Queu[] = [];
private _paused = false;
private _emptyFunction: Function;
private _executedFunction: Function;
private _executionFinishFunction: Function;
private options: OptionsConstructor = {};
constructor(options: OptionsConstructor = {}) {
Object.assign(this.options, { maxConcurrency: 1 }, options);
}
get isPaused() {
return this._paused;
}
get pending() {
return this._queu.length;
}
get queu() {
return this._queu;
}
/**
* Return important elements of queu element
* @param queuElement
* @returns
*/
private computeQueuElement(queuElement: Queu) {
return {
fn: queuElement.fn,
arguments: queuElement.arguments,
priority: queuElement.priority,
};
}
/**
* Start the next element from the queu
*/
private startNextQueuElement() {
const actual = this._queu.find((e) => !e.started);
if (!actual) return;
actual.started = true;
if (this._executedFunction)
this._executedFunction(this.computeQueuElement(actual));
actual
.fn(...actual.arguments)
.then(actual.resolve)
.catch(actual.reject)
.finally(() => {
//after execution we remove it
const index = this._queu.findIndex((e) => e === actual);
this._queu.splice(index, 1);
if (this._executionFinishFunction)
this._executionFinishFunction(this.computeQueuElement(actual));
if (this._queu.length > 0) this.startNextQueuElement();
else if (this._emptyFunction) this._emptyFunction();
});
}
/**
* Same as createFn but directly add the function to the queu
* @param fn
* @param Options
*/
public async add(fn: Function, options: Options = {}) {
const queuFn = this.createFn(fn, options);
return await queuFn();
}
/**
* Create a function that register other function in the queu
*/
public createFn(fn: Function, options: Options = {}) {
const obj = this; //no function arrow because we need access to "arguments"
return function () {
return new Promise((resolve, reject) => {
if (obj._queu.length === obj.options.maxSize) return; //if max size
// adding to the queu
const queuElement = {
fn,
arguments,
priority: options.priority ?? Infinity,
resolve,
reject,
};
obj._queu.push(queuElement);
//sorting by priority
if (options.priority) obj._queu.sort((a, b) => a.priority - b.priority);
//starting the function
if (obj._queu.length <= obj.options.maxConcurrency && !obj._paused) {
obj.startNextQueuElement();
}
});
};
}
/**
* Remove element from the queu
* @param fn
* @returns
*/
public remove(fn: Function) {
const index = this._queu.findIndex((e) => e.fn === fn);
if (index === -1) return false;
this.removeFromIndex(index);
return true;
}
/**
* Remove element from queu with the index
* @param index
*/
public removeFromIndex(index: number) {
this._queu.splice(index, 1);
}
/**
* Clear the actual queu
*/
public clear() {
this._queu = [];
}
/**
* Pause the queu
*/
public pause() {
this._paused = true;
}
/**
* Start after pause
*/
public start() {
this._paused = false;
if (this._queu.length > 0) this.startNextQueuElement();
}
/**
* When the queu is empty
* @param fn
*/
public onEmpty(fn: Function) {
this._emptyFunction = fn;
}
/**
* When a function is executed
* @param fn
*/
public onQueuElementExecuted(fn: Function) {
this._executedFunction = fn;
}
/**
* When a function have finish to be executed
* @param fn
*/
public onQueuElementFinishExecution(fn: Function) {
this._executionFinishFunction = fn;
}
}
export default Enqueu;