export class Mutex { private value: T; private taskExecuting = false; private taskQueue = []; private freeListener = []; constructor(value: T) { this.value = value; } isFree() : boolean { return !this.taskExecuting && this.taskQueue.length === 0; } awaitFree() : Promise { return new Promise(resolve => this.freeListener.push(resolve)); } execute(callback: (value: T, setValue: (newValue: T) => void) => R | Promise) : Promise { return new Promise((resolve, reject) => { this.taskQueue.push(() => new Promise(taskResolve => { try { const result = callback(this.value, newValue => this.value = newValue); if(result instanceof Promise) { result.then(result => { taskResolve(); resolve(result); }).catch(error => { taskResolve(); reject(error); }); } else { taskResolve(); resolve(result); } } catch (error) { taskResolve(); reject(error); } })); if(!this.taskExecuting) { this.executeNextTask(); } }); } async tryExecute(callback: (value: T, setValue: (newValue: T) => void) => R | Promise) : Promise<{ status: "success", result: R } | { status: "would-block" }> { if(!this.taskExecuting) { return { status: "success", result: await this.execute(callback) }; } else { return { status: "would-block" }; } } private executeNextTask() { const task = this.taskQueue.pop_front(); if(typeof task === "undefined") { this.taskExecuting = false; this.triggerFinished(); return; } this.taskExecuting = true; task().then(() => this.executeNextTask()); } private triggerFinished() { while(this.isFree()) { const listener = this.freeListener.pop_front(); if(!listener) { break; } try { listener(); } catch (error) { console.error(error); } } } }