import * as plugins from './lik.plugins.js'; interface IExecutionSlot { executionDeferred: plugins.smartpromise.Deferred; funcToExecute: () => Promise; timeout?: number; mode: 'exclusive' | 'nonexclusive'; } /** * allows for avoiding race condition */ export class AsyncExecutionStack { private executionSlots: IExecutionSlot[] = []; public async getExclusiveExecutionSlot(funcArg: () => Promise, timeoutArg?: number) { const executionDeferred = plugins.smartpromise.defer(); const executionSlot: IExecutionSlot = { funcToExecute: funcArg, executionDeferred, timeout: timeoutArg, mode: 'exclusive', }; this.executionSlots.push(executionSlot); this.processExecutionSlots(); return executionDeferred.promise; } public async getNonExclusiveExecutionSlot( funcArg: () => Promise, timeoutArg?: number ) { const executionDeferred = plugins.smartpromise.defer(); const executionSlot: IExecutionSlot = { funcToExecute: funcArg, executionDeferred, timeout: timeoutArg, mode: 'nonexclusive', }; this.executionSlots.push(executionSlot); this.processExecutionSlots(); return executionDeferred.promise; } private currentlyExecutingDeferred: plugins.smartpromise.Deferred; private async processExecutionSlots() { if (this.currentlyExecutingDeferred) { return; } this.currentlyExecutingDeferred = plugins.smartpromise.defer(); let nonExclusiveRunningSlots: IExecutionSlot[] = []; const checkNonExclusiveRunningSlots = async (cleanArg = false) => { if (nonExclusiveRunningSlots.length > 100 || cleanArg) { await Promise.all(nonExclusiveRunningSlots.map(nonExclusiveRunningSlotArg => nonExclusiveRunningSlotArg.executionDeferred.promise)); nonExclusiveRunningSlots = []; } }; while (this.executionSlots.length > 0) { const nextExecutionSlot = this.executionSlots.shift(); const runNextExecution = async () => { if (nextExecutionSlot.timeout) { const result = await Promise.race([ nextExecutionSlot.funcToExecute(), plugins.smartdelay.delayFor(nextExecutionSlot.timeout), ]); nextExecutionSlot.executionDeferred.resolve(result); } else { nextExecutionSlot.executionDeferred.resolve(await nextExecutionSlot.funcToExecute()); } }; if (nextExecutionSlot.mode === 'exclusive') { await checkNonExclusiveRunningSlots(true); await runNextExecution(); } else { nonExclusiveRunningSlots.push(nextExecutionSlot); await checkNonExclusiveRunningSlots(false); runNextExecution(); } } this.currentlyExecutingDeferred.resolve(); this.currentlyExecutingDeferred = null; } }