typescriptutilityperformanceevents
Throttle Function
Classic throttle utility with TypeScript generics. Supports leading and trailing invocations with cancel support.
Code
interface ThrottleOptions {
leading?: boolean;
trailing?: boolean;
}
function throttle<T extends (...args: unknown[]) => unknown>(
fn: T,
interval: number,
options: ThrottleOptions = {}
): (...args: Parameters<T>) => void {
const { leading = true, trailing = true } = options;
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let lastCallTime = 0;
let lastArgs: Parameters<T> | null = null;
let lastThis: unknown = null;
return function (this: unknown, ...args: Parameters<T>) {
const now = Date.now();
const elapsed = now - lastCallTime;
lastArgs = args;
lastThis = this;
if (elapsed >= interval) {
// Enough time has passed — invoke immediately if leading is enabled
if (leading) {
lastCallTime = now;
fn.apply(this, args);
lastArgs = null;
}
// Clear any pending trailing call
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
// Schedule trailing call if leading was skipped
if (!leading && trailing) {
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
fn.apply(lastThis, lastArgs!);
lastArgs = null;
timeoutId = null;
}, interval);
}
}
// Schedule trailing invocation
if (trailing && !timeoutId && elapsed < interval) {
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
if (lastArgs) fn.apply(lastThis, lastArgs);
lastArgs = null;
timeoutId = null;
}, interval - elapsed);
}
};
}
// Usage
const throttledScroll = throttle((e: Event) => {
console.log('Scroll position:', window.scrollY);
}, 200);
window.addEventListener('scroll', throttledScroll);Variations
With cancel support
interface ThrottledFn<T extends (...args: unknown[]) => unknown> {
(...args: Parameters<T>): void;
cancel: () => void;
flush: () => void;
}
function throttle<T extends (...args: unknown[]) => unknown>(
fn: T,
interval: number
): ThrottledFn<T> {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let lastCallTime = 0;
let lastArgs: Parameters<T> | null = null;
let lastThis: unknown = null;
const throttled = function (this: unknown, ...args: Parameters<T>) {
const now = Date.now();
const remaining = interval - (now - lastCallTime);
lastArgs = args;
lastThis = this;
if (remaining <= 0) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastCallTime = now;
fn.apply(this, args);
lastArgs = null;
} else if (!timeoutId) {
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
if (lastArgs) fn.apply(lastThis, lastArgs);
lastArgs = null;
timeoutId = null;
}, remaining);
}
} as ThrottledFn<T>;
throttled.cancel = () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastArgs = null;
lastCallTime = 0;
};
throttled.flush = () => {
if (timeoutId && lastArgs) {
clearTimeout(timeoutId);
timeoutId = null;
lastCallTime = Date.now();
fn.apply(lastThis, lastArgs);
lastArgs = null;
}
};
return throttled;
}
// Usage
const throttled = throttle(handleResize, 250);
window.addEventListener('resize', throttled);
// Later: cancel pending invocations
throttled.cancel();
// Or: immediately execute pending call
throttled.flush();