class ExecQueue {
public readonly queuedCalls: Function[]=[];
public isRunning: boolean= false;
public cancelAllQueuedCalls(): number {
let n: number;
this.queuedCalls.splice(0, n=this.queuedCalls.length);
return n;
}
public prComplete: Promise<void>= Promise.resolve();
}
const globalContext: Object = {};
const clusters = new WeakMap<Object, WeakMap<GroupRef,ExecQueue>>();
function getOrCreateExecQueue( context: Object, groupRef: GroupRef): ExecQueue {
let execQueueByGroup = clusters.get(context);
if (!execQueueByGroup) { execQueueByGroup = new WeakMap(); clusters.set(context, execQueueByGroup); }
let execQueue= execQueueByGroup.get(groupRef);
if (!execQueue){ execQueue= new ExecQueue(); execQueueByGroup.set(groupRef, execQueue); }
return execQueue;
}
export type GroupRef = never[];
export function createGroupRef(): GroupRef { return new Array<never>(0);}
export function build<T extends (...input: any[]) => Promise<any>>(fun: T): T;export function build<T extends (...input: any[]) => Promise<any>>(groupRef: GroupRef, fun: T): T;export function build(...inputs: any[]): any {
switch (inputs.length) { case 1: return buildFnPromise(true, createGroupRef(), inputs[0]); case 2: return buildFnPromise(true, inputs[0], inputs[1]); }
}
export function buildMethod<T extends (...input: any[]) => Promise<any>>(fun: T): T;export function buildMethod<T extends (...input: any[]) => Promise<any>>(groupRef: GroupRef, fun: T): T;export function buildMethod(...inputs: any[]): any {
switch (inputs.length) { case 1: return buildFnPromise(false, createGroupRef(), inputs[0]); case 2: return buildFnPromise(false, inputs[0], inputs[1]); }
}
export function getQueuedCallCount( runExclusiveFunction: Function, classInstanceObject?: Object): number {
const execQueue= getExecQueueByFunctionAndContext(runExclusiveFunction, classInstanceObject);
return execQueue?execQueue.queuedCalls.length:0;
}
export function cancelAllQueuedCalls( runExclusiveFunction: Function, classInstanceObject?: Object): number {
const execQueue= getExecQueueByFunctionAndContext(runExclusiveFunction, classInstanceObject);
return execQueue?execQueue.cancelAllQueuedCalls():0;
}
export function isRunning( runExclusiveFunction: Function, classInstanceObject?: Object): boolean {
const execQueue= getExecQueueByFunctionAndContext(runExclusiveFunction, classInstanceObject);
return execQueue?execQueue.isRunning:false;
}
export function getPrComplete( runExclusiveFunction: Function, classInstanceObject?: Object): Promise<void>{
const execQueue= getExecQueueByFunctionAndContext(runExclusiveFunction, classInstanceObject);
return execQueue?execQueue.prComplete:Promise.resolve();
}
const groupByRunExclusiveFunction= new WeakMap<Function, GroupRef>();
function getExecQueueByFunctionAndContext( runExclusiveFunction: Function, context = globalContext): ExecQueue | undefined {
const groupRef= groupByRunExclusiveFunction.get(runExclusiveFunction);
if( !groupRef ){ throw Error("Not a run exclusiveFunction"); }
const execQueueByGroup= clusters.get(context);
if( !execQueueByGroup ){ return undefined; }
return execQueueByGroup.get(groupRef)!;
}
function buildFnPromise<T extends (...inputs: any[]) => Promise<any>>( isGlobal: boolean, groupRef: GroupRef, fun: T): T {
let execQueue: ExecQueue;
const runExclusiveFunction = (function (this: any, ...inputs) {
if (!isGlobal) {
if (!(this instanceof Object)) { throw new Error("Run exclusive, <this> should be an object"); }
execQueue = getOrCreateExecQueue(this, groupRef);
}
return new Promise<any>((resolve, reject) => {
let onPrCompleteResolve: () => void;
execQueue.prComplete = new Promise(resolve => onPrCompleteResolve = () => resolve() );
const onComplete = (result: { data: any } | { reason: any }) => {
onPrCompleteResolve();
execQueue.isRunning = false;
if (execQueue.queuedCalls.length) { execQueue.queuedCalls.shift()!(); }
if ("data" in result) { resolve(result.data); } else { reject(result.reason); }
};
(function callee(this: any,...inputs: any[]) {
if (execQueue.isRunning) { execQueue.queuedCalls.push(() => callee.apply(this, inputs)); return; }
execQueue.isRunning = true;
try {
fun.apply(this, inputs) .then(data => onComplete({ data })) .catch(reason => onComplete({ reason })) ;
} catch (error) {
onComplete({ "reason": error });
}
}).apply(this, inputs);
});
}) as T;
if (isGlobal) {
execQueue = getOrCreateExecQueue(globalContext, groupRef);
}
groupByRunExclusiveFunction.set(runExclusiveFunction, groupRef);
return runExclusiveFunction;
}
export function buildCb<T extends (...input: any[]) => void>(fun: T): T;export function buildCb<T extends (...input: any[]) => void>(groupRef: GroupRef, fun: T): T;export function buildCb(...inputs: any[]): any {
switch (inputs.length) { case 1: return buildFnCallback(true, createGroupRef(), inputs[0]); case 2: return buildFnCallback(true, inputs[0], inputs[1]); }
}
export function buildMethodCb<T extends (...input: any[]) => void>(fun: T): T;export function buildMethodCb<T extends (...input: any[]) => void>(groupRef: GroupRef, fun: T): T;export function buildMethodCb(...inputs: any[]): any {
switch (inputs.length) { case 1: return buildFnCallback(false, createGroupRef(), inputs[0]); case 2: return buildFnCallback(false, inputs[0], inputs[1]); }
}
function buildFnCallback<T extends (...inputs: any[]) => Promise<any>>( isGlobal: boolean, groupRef: GroupRef, fun: T): T {
let execQueue: ExecQueue;
const runExclusiveFunction = (function (this: any,...inputs) {
if (!isGlobal) {
if (!(this instanceof Object)) { throw new Error("Run exclusive, <this> should be an object"); }
execQueue = getOrCreateExecQueue(this, groupRef);
}
let callback: Function | undefined = undefined;
if (inputs.length && typeof inputs[inputs.length - 1] === "function") { callback = inputs.pop(); }
let onPrCompleteResolve: () => void;
execQueue.prComplete = new Promise(resolve => onPrCompleteResolve = () => resolve() );
const onComplete = (...inputs: any[]) => { onPrCompleteResolve();
execQueue!.isRunning = false;
if (execQueue.queuedCalls.length) { execQueue.queuedCalls.shift()!(); }
if (callback) { callback.apply(this, inputs); }
};
(onComplete as any).hasCallback = !!callback;
(function callee(this: any, ...inputs: any[]) {
if (execQueue.isRunning) { execQueue.queuedCalls.push(() => callee.apply(this, inputs)); return; }
execQueue.isRunning = true;
try {
fun.apply(this, [...inputs, onComplete]);
} catch (error) {
error.message += " ( This exception should not have been thrown, miss use of run-exclusive buildCb )";
throw error;
}
}).apply(this, inputs);
}) as T;
if (isGlobal) {
execQueue = getOrCreateExecQueue(globalContext, groupRef);
}
groupByRunExclusiveFunction.set(runExclusiveFunction, groupRef);
return runExclusiveFunction;
}