export type KeyedAsyncQueueHooks = { onEnqueue?: () => void; onSettle?: () => void; }; export function enqueueKeyedTask(params: { tails: Map>; key: string; task: () => Promise; hooks?: KeyedAsyncQueueHooks; }): Promise { params.hooks?.onEnqueue?.(); const previous = params.tails.get(params.key) ?? Promise.resolve(); const current = previous .catch(() => undefined) .then(params.task) .finally(() => { params.hooks?.onSettle?.(); }); const tail = current.then( () => undefined, () => undefined, ); params.tails.set(params.key, tail); void tail.finally(() => { if (params.tails.get(params.key) === tail) { params.tails.delete(params.key); } }); return current; } export class KeyedAsyncQueue { private readonly tails = new Map>(); getTailMapForTesting(): Map> { return this.tails; } enqueue(key: string, task: () => Promise, hooks?: KeyedAsyncQueueHooks): Promise { return enqueueKeyedTask({ tails: this.tails, key, task, ...(hooks ? { hooks } : {}), }); } }