export class AsyncLock { private permits: number = 1; private promiseResolverQueue: Array<(v: boolean) => void> = []; async inLock(func: () => Promise | T): Promise { try { await this.lock(); return await func(); } finally { this.unlock(); } } private async lock() { if (this.permits > 0) { this.permits = this.permits - 1; return; } await new Promise(resolve => this.promiseResolverQueue.push(resolve)); } private unlock() { this.permits += 1; if (this.permits > 1 && this.promiseResolverQueue.length > 0) { throw new Error('this.permits should never be > 0 when there is someone waiting.'); } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) { // If there is someone else waiting, immediately consume the permit that was released // at the beginning of this function and let the waiting function resume. this.permits -= 1; const nextResolver = this.promiseResolverQueue.shift(); // Resolve on the next tick if (nextResolver) { setTimeout(() => { nextResolver(true); }, 0); } } } }