/** * Sperren je Schluessel (z. B. Kundennummer, Ticket-Id), damit parallele Anfragen fuer * dieselbe Ressource nacheinander laufen, waehrend Anfragen zu anderen Schluesseln parallel * bleiben. Ein Aufruf kann mehrere Schluessel auf einmal sperren (alle muessen frei werden). * waitMs begrenzt, wie lange auf die aktuellen Inhaber gewartet wird (Default: unbegrenzt); * nach Ablauf laeuft fn trotzdem los, ohne weiter zu warten. * * Umsetzung: je Schluessel eine Promise-Kette (das "tail") in einer modulweiten Map. */ const tailsByKey = new Map() // setTimeout(fn, Infinity) wuerde Node zu 1 ms clampen (samt Warnung) - hier bewusst // eine Promise, die nie aufloest, damit "unbegrenzt warten" auch wirklich unbegrenzt ist. const sleep = (ms) => ms === Infinity ? new Promise(() => {}) : new Promise((resolve) => setTimeout(resolve, ms)) export async function withLocks(keys, fn, { waitMs = Infinity } = {}) { const previousTails = keys.map((key) => tailsByKey.get(key)).filter(Boolean) let releaseTail const tail = new Promise((resolve) => { releaseTail = resolve }) for (const key of keys) tailsByKey.set(key, tail) await Promise.race([Promise.allSettled(previousTails), sleep(waitMs)]) try { return await fn() } finally { releaseTail() for (const key of keys) { if (tailsByKey.get(key) === tail) tailsByKey.delete(key) } } }