You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
89 lines
2.4 KiB
89 lines
2.4 KiB
//#region src/index.ts
|
|
const DEBOUNCE_DEFAULTS = { trailing: true };
|
|
/**
|
|
Debounce functions
|
|
@param fn - Promise-returning/async function to debounce.
|
|
@param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
|
|
@returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
|
|
@example
|
|
```
|
|
import { debounce } from 'perfect-debounce';
|
|
const expensiveCall = async input => input;
|
|
const debouncedFn = debounce(expensiveCall, 200);
|
|
for (const number of [1, 2, 3]) {
|
|
console.log(await debouncedFn(number));
|
|
}
|
|
//=> 1
|
|
//=> 2
|
|
//=> 3
|
|
```
|
|
*/
|
|
function debounce(fn, wait = 25, options = {}) {
|
|
options = {
|
|
...DEBOUNCE_DEFAULTS,
|
|
...options
|
|
};
|
|
if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
|
|
let leadingValue;
|
|
let timeout;
|
|
let resolveList = [];
|
|
let currentPromise;
|
|
let trailingArgs;
|
|
const applyFn = (_this, args) => {
|
|
currentPromise = _applyPromised(fn, _this, args);
|
|
currentPromise.finally(() => {
|
|
currentPromise = null;
|
|
if (options.trailing && trailingArgs && !timeout) {
|
|
const promise = applyFn(_this, trailingArgs);
|
|
trailingArgs = null;
|
|
return promise;
|
|
}
|
|
});
|
|
return currentPromise;
|
|
};
|
|
const debounced = function(...args) {
|
|
if (options.trailing) trailingArgs = args;
|
|
if (currentPromise) return currentPromise;
|
|
return new Promise((resolve) => {
|
|
const shouldCallNow = !timeout && options.leading;
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => {
|
|
timeout = null;
|
|
const promise = options.leading ? leadingValue : applyFn(this, args);
|
|
trailingArgs = null;
|
|
for (const _resolve of resolveList) _resolve(promise);
|
|
resolveList = [];
|
|
}, wait);
|
|
if (shouldCallNow) {
|
|
leadingValue = applyFn(this, args);
|
|
resolve(leadingValue);
|
|
} else resolveList.push(resolve);
|
|
});
|
|
};
|
|
const _clearTimeout = (timer) => {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timeout = null;
|
|
}
|
|
};
|
|
debounced.isPending = () => !!timeout;
|
|
debounced.cancel = () => {
|
|
_clearTimeout(timeout);
|
|
resolveList = [];
|
|
trailingArgs = null;
|
|
};
|
|
debounced.flush = () => {
|
|
_clearTimeout(timeout);
|
|
if (!trailingArgs || currentPromise) return;
|
|
const args = trailingArgs;
|
|
trailingArgs = null;
|
|
return applyFn(this, args);
|
|
};
|
|
return debounced;
|
|
}
|
|
async function _applyPromised(fn, _this, args) {
|
|
return await fn.apply(_this, args);
|
|
}
|
|
|
|
//#endregion
|
|
export { debounce };
|