@monstermann/fn
Version:
A utility library for TypeScript.
111 lines (109 loc) • 3.07 kB
JavaScript
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 };