UNPKG

@monstermann/fn

Version:

A utility library for TypeScript.

111 lines (109 loc) 3.07 kB
import { clearTimer, createTimer, restartTimer, startTimer } from "./internals/timers.js"; //#region src/promise/debounce.ts /** * `debounce(fn, options)` * * Creates a debounced function that delays invoking `fn` until after `options.wait` milliseconds have elapsed since the last time the debounced function was invoked. * * Implementation details: * * - Only one `fn` can run at any given time, asynchronous functions can not conflict with each other * - Pending calls are not accumulated in an internal array and asynchronously resolved * * ```ts * const debouncedSave = debounce(saveData, { * wait: 1000, * // Maximum time to wait after the first call, default: undefined * maxWait?: 5000, * // Invoke function on the leading edge, default: false * leading?: false, * // Invoke function on the trailing edge, default: true * trailing?: true, * }); * * // void * debouncedSave(data); * * // Cancel pending execution - does not cancel current invocation * debouncedSave.clear(); * * // Execute pending invocation asap * debouncedSave.flush(); * * debouncedSave.isIdle(); * debouncedSave.isPending(); * debouncedSave.isRunning(); * * // Wait until idle, if a `gracePeriod` is given then wait * // until it has been idle for `gracePeriod` milliseconds */ function debounce(fn, options) { let isRunning = false; let isPending = false; let flushAsap = false; let nextArgs; const idleTimers = /* @__PURE__ */ new Set(); const waitTimer = createTimer(options.wait, options.trailing === false ? clear : invoke); const maxWaitTimer = createTimer(options.maxWait ?? -1, invoke); function clear() { if (!isPending) return; isPending = false; nextArgs = void 0; clearTimer(waitTimer); clearTimer(maxWaitTimer); if (!isRunning) restartTimer(idleTimers); } async function invoke() { const args = nextArgs; if (isRunning || !isPending || !args) return; isRunning = true; isPending = false; nextArgs = void 0; clearTimer(waitTimer); clearTimer(maxWaitTimer); clearTimer(idleTimers); try { await fn(...args); } finally { isRunning = false; if (isPending && flushAsap) invoke(); else if (isPending) { restartTimer(waitTimer); restartTimer(maxWaitTimer); } else restartTimer(idleTimers); } } function debounced(...args) { isPending = true; nextArgs = args; if (isRunning) return; if (!isPending && !isRunning && options.leading) { invoke(); return; } clearTimer(idleTimers); restartTimer(waitTimer); startTimer(maxWaitTimer); } debounced.clear = clear; debounced.flush = function() { if (!isPending) return; if (!isRunning) invoke(); else flushAsap = true; }; debounced.idle = function(gracePeriod = 0) { return new Promise((resolve) => { const t = createTimer(gracePeriod, () => { idleTimers.delete(t); resolve(); }); idleTimers.add(t); }); }; debounced.isIdle = () => !isPending && !isRunning; debounced.isPending = () => isPending; debounced.isRunning = () => isRunning; return debounced; } //#endregion export { debounce };