debounce
The debounce utility creates a version of a function that delays its execution until a specified amount of time has passed since it was last called. This is ideal for handling rapid-fire events like window resizing, scrolling, or keystrokes.
Implementation
View Source Code
ts
import type { Fn } from '../types';
import { assert } from './assert';
export type Debounced<T extends Fn> = ((this: ThisParameterType<T>, ...args: Parameters<T>) => void) & {
cancel(): void;
flush(): ReturnType<T> | undefined;
pending(): boolean;
};
/**
* Debounce a function (trailing). Use `flush` to invoke immediately,
* `cancel` to clear, and `pending` to check if an invocation is scheduled.
*/
export function debounce<T extends Fn>(fn: T, delay = 300): Debounced<T> {
assert(typeof fn === 'function', 'First argument must be a function', {
args: { fn },
type: TypeError,
});
assert(typeof delay === 'number' && delay >= 0, 'Delay must be a non-negative number', {
args: { delay },
type: TypeError,
});
let timer: ReturnType<typeof setTimeout> | undefined;
let lastArgs: Parameters<T> | undefined;
let lastThis: ThisParameterType<T> | undefined;
let lastResult: ReturnType<T> | undefined;
const clearTimer = () => {
if (timer !== undefined) {
clearTimeout(timer);
timer = undefined;
}
};
const invoke = () => {
clearTimer();
if (!lastArgs) return undefined; // nothing to invoke
const args = lastArgs;
const ctx = lastThis as ThisParameterType<T>;
lastArgs = undefined;
lastThis = undefined;
// biome-ignore lint/suspicious/noExplicitAny: -
lastResult = fn.apply(ctx as any, args);
return lastResult;
};
const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>) {
lastArgs = args;
lastThis = this;
clearTimer();
timer = setTimeout(invoke, delay);
} as Debounced<T>;
debounced.cancel = () => {
clearTimer();
lastArgs = undefined;
lastThis = undefined;
};
debounced.flush = () => invoke() as ReturnType<T> | undefined;
debounced.pending = () => timer !== undefined;
return debounced;
}Features
- Isomorphic: Works in both Browser and Node.js.
- Efficient: Prevents unnecessary processing by grouping multiple calls into one.
- Type-safe: Preserves the argument types of the original function.
API
ts
type DebouncedFunction = {
(...args: any[]): void;
cancel: () => void;
flush: () => void;
};
function debounce<T extends (...args: any[]) => any>(fn: T, wait?: number): DebouncedFunction;Parameters
fn: The function you want to debounce.wait: The number of milliseconds to wait for "silence" before actually callingfn(defaults to300).
Returns
- A debounced function with two additional methods:
cancel(): Cancels any pending execution.flush(): Immediately executes any pending call and cancels the timer.
Examples
Search Input Handling
ts
import { debounce } from '@vielzeug/toolkit';
const search = debounce((query: string) => {
console.log('Searching for:', query);
// Perform API call here
}, 300);
// Only the last call will execute after 300ms of inactivity
search('a');
search('ap');
search('app');
search('apple');Window Resize
ts
import { debounce } from '@vielzeug/toolkit';
const handleResize = debounce(() => {
console.log('Window resized!');
// Recalculate layout
}, 250);
window.addEventListener('resize', handleResize);Using Cancel and Flush
ts
import { debounce } from '@vielzeug/toolkit';
const saveData = debounce((data) => {
console.log('Saving:', data);
}, 1000);
saveData({ name: 'Alice' });
saveData({ name: 'Bob' });
// Cancel pending save
saveData.cancel();
// Or immediately execute pending save
saveData({ name: 'Charlie' });
saveData.flush(); // Saves 'Charlie' immediatelyImplementation Notes
- The debounced function does not return the result of the original
fn, as execution is asynchronous. - Each call to the debounced function clears any existing timer and starts a new one.
- In a Node.js environment, it uses
setTimeoutunder the hood.