37 lines
1.4 KiB
JavaScript
37 lines
1.4 KiB
JavaScript
/**
|
|
* 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)
|
|
}
|
|
}
|
|
}
|