debounce
Creates a debounced version of a function with support for immediate execution and manual cancellation.
1/**
2 * Creates a debounced version of a function with support for immediate execution
3 * and manual cancellation.
4 *
5 * @param fn - The function to debounce.
6 * @param delay - Delay in milliseconds.
7 * @param immediate - If true, triggers the function on the leading edge.
8 * @returns A debounced function with a cancel method.
9 */
10export function debounce<T extends (...args: any[]) => void>(
11 fn: T,
12 delay: number,
13 immediate = false
14): ((...args: Parameters<T>) => void) & { cancel: () => void } {
15 let timeoutId: ReturnType<typeof setTimeout> | undefined;
16 let called = false;
17
18 const debounced = (...args: Parameters<T>) => {
19 const callNow = immediate && !called;
20
21 if (timeoutId) clearTimeout(timeoutId);
22
23 timeoutId = setTimeout(() => {
24 timeoutId = undefined;
25 if (!immediate) fn(...args);
26 called = false;
27 }, delay);
28
29 if (callNow) {
30 fn(...args);
31 called = true;
32 }
33 };
34
35 debounced.cancel = () => {
36 if (timeoutId) {
37 clearTimeout(timeoutId);
38 timeoutId = undefined;
39 }
40 called = false;
41 };
42
43 return debounced;
44}
Delay-Based Throttling
Limits how frequently a function can execute by grouping rapid calls into a single invocation, reducing unnecessary processing.
Supports Leading Edge Execution
The
immediate
flag allows execution on the first call, which is useful for responsive UIs and instant feedback.Manual Cancellation
Provides a
.cancel()
method to programmatically clear the pending execution, adding flexibility in teardown or reset scenarios.Type-Safe and Flexible
Uses TypeScript generics to preserve argument and return types for any input function, improving developer experience and safety.
Non-Intrusive and Pure
Wraps any function without modifying its behavior outside of timing, making it a non-invasive utility.
Tests | Examples
1jest.useFakeTimers();
2
3test('debounce - should delay execution', () => {
4 const fn = jest.fn();
5 const debounced = debounce(fn, 1000);
6
7 debounced();
8 debounced();
9 debounced();
10
11 expect(fn).not.toBeCalled();
12
13 jest.advanceTimersByTime(1000);
14
15 expect(fn).toBeCalledTimes(1);
16});
17
18test('debounce - should call immediately when immediate=true', () => {
19 const fn = jest.fn();
20 const debounced = debounce(fn, 1000, true);
21
22 debounced('first');
23 debounced('second');
24 debounced('third');
25
26 expect(fn).toHaveBeenCalledTimes(1);
27 expect(fn).toHaveBeenCalledWith('first');
28
29 jest.advanceTimersByTime(1000);
30
31 debounced('afterWait');
32 expect(fn).toHaveBeenCalledTimes(2);
33 expect(fn).toHaveBeenLastCalledWith('afterWait');
34});
35
36test('debounce - should cancel pending call', () => {
37 const fn = jest.fn();
38 const debounced = debounce(fn, 1000);
39
40 debounced();
41 debounced.cancel();
42
43 jest.advanceTimersByTime(1000);
44 expect(fn).not.toBeCalled();
45});
Common Use Cases
Input Event Optimization
Prevent excessive API calls or UI updates when users type into a search box or input field.
Window Resize or Scroll Handlers
Reduce frequency of layout recalculations or heavy computations tied to rapid DOM events.
Auto-Save or Auto-Sync Features
Debounce form or editor changes before triggering save actions to minimize network load.
Analytics and Tracking
Limit frequency of logging or tracking events tied to user interaction or navigation.
Component Cleanup in React/Vue
Use
.cancel()
during unmounting to avoid triggering state updates on unmounted components.