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