Tofu/app/services/scheduling/createSaveScheduler.js
2026-09-18 01:41:02 +02:00

56 lines
1.6 KiB
JavaScript

// Debounced save scheduler: it owns a timer, so it is a factory with a
// destroy() that the caller must run when it unmounts.
//
// The pending call and the timer live in one `state` object mutated only by
// the named functions below — never by a callback closing over a loose
// variable.
function cancelTimer(state) {
if (state.timerId === null) return
clearTimeout(state.timerId)
state.timerId = null
}
function takePendingRun(state) {
const pendingRun = state.pendingRun
state.pendingRun = null
return pendingRun
}
// Returns a promise that never rejects: a failing save is already reported by
// the store's result object, and a thrown callback must not crash the UI.
async function runPending(pendingRun) {
try {
await pendingRun()
} catch (error) {
console.error('Scheduled save failed', error)
}
}
export function createSaveScheduler({ delayMs = 1200 } = {}) {
const state = { timerId: null, pendingRun: null }
// Always returns a promise, so callers can await it without testing
// whether something was pending.
function flush() {
cancelTimer(state)
const pendingRun = takePendingRun(state)
if (pendingRun === null) return Promise.resolve()
return runPending(pendingRun)
}
// Replaces the call waiting in line: only the last one ever runs.
function schedule(run) {
cancelTimer(state)
state.pendingRun = run
state.timerId = setTimeout(flush, delayMs)
}
// Cancels: the pending call is dropped on purpose, destroy() does not save.
function destroy() {
cancelTimer(state)
takePendingRun(state)
}
return { schedule, flush, destroy }
}