UNPKG

@monstermann/fn

Version:

A utility library for TypeScript.

103 lines (101 loc) 2.73 kB
import { clearTimer, createTimer, restartTimer, startTimer } from "./internals/timers.js"; //#region src/promise/throttle.ts /** * `throttle(fn, options)` * * Creates a throttled function that limits how often `fn` can be invoked based on `options.wait` milliseconds. * * 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 throttledSave = throttle(saveData, { * wait: 1000, * // Invoke function on the leading edge, default: false * leading?: false, * // Invoke function on the trailing edge, default: true * trailing?: true, * }); * * // void * throttledSave(data); * * // Cancel pending execution - does not cancel current invocation * throttledSave.clear(); * * // Execute pending invocation asap * throttledSave.flush(); * * throttledSave.isIdle(); * throttledSave.isPending(); * throttledSave.isRunning(); * * // Wait until idle, if a `gracePeriod` is given then wait * // until it has been idle for `gracePeriod` milliseconds */ function throttle(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); function clear() { if (!isPending) return; isPending = false; nextArgs = void 0; clearTimer(waitTimer); 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(idleTimers); try { await fn(...args); } finally { isRunning = false; if (isPending && flushAsap) invoke(); else if (isPending) startTimer(waitTimer); else restartTimer(idleTimers); } } function throttled(...args) { isPending = true; nextArgs = args; if (isRunning) return; if (!isPending && !isRunning && options.leading) { invoke(); return; } clearTimer(idleTimers); startTimer(waitTimer); } throttled.clear = clear; throttled.flush = function() { if (!isPending) return; if (!isRunning) invoke(); else flushAsap = true; }; throttled.idle = function(gracePeriod = 0) { return new Promise((resolve) => { const t = createTimer(gracePeriod, () => { idleTimers.delete(t); resolve(); }); idleTimers.add(t); }); }; throttled.isIdle = () => !isPending && !isRunning; throttled.isPending = () => isPending; throttled.isRunning = () => isRunning; return throttled; } //#endregion export { throttle };