40 lines
1.2 KiB
JavaScript
40 lines
1.2 KiB
JavaScript
// Serialisiert asynchrone Aufgaben je `key` und koalesziert Wartende:
|
|
// - Es läuft nie mehr als eine Aufgabe pro Key gleichzeitig.
|
|
// - Kommen während eines Laufs weitere Aufrufe rein, wird GENAU EIN weiterer
|
|
// Durchlauf nachgelagert (egal wie viele warten) — sie teilen sich dessen
|
|
// Ergebnis. So sehen alle den jüngsten Stand, ohne einen Lauf-Sturm.
|
|
//
|
|
// Einsatz: teure, idempotente Vorgänge wie der Hugo-Build (siehe hugo.js).
|
|
const state = new Map();
|
|
|
|
export function coalesce(key, fn) {
|
|
let s = state.get(key);
|
|
if (!s) { s = { running: false, rerun: false, fn, waiters: [] }; state.set(key, s); }
|
|
s.fn = fn; // jüngste Variante gewinnt für den nächsten Lauf
|
|
return new Promise((resolve, reject) => {
|
|
s.waiters.push({ resolve, reject });
|
|
if (!s.running) drain(key);
|
|
else s.rerun = true;
|
|
});
|
|
}
|
|
|
|
async function drain(key) {
|
|
const s = state.get(key);
|
|
s.running = true;
|
|
try {
|
|
do {
|
|
s.rerun = false;
|
|
const waiters = s.waiters;
|
|
s.waiters = [];
|
|
try {
|
|
const r = await s.fn();
|
|
waiters.forEach((w) => w.resolve(r));
|
|
} catch (e) {
|
|
waiters.forEach((w) => w.reject(e));
|
|
}
|
|
} while (s.rerun);
|
|
} finally {
|
|
s.running = false;
|
|
}
|
|
}
|