📚 4 min read
Debouncing Examples ​
Learn how to implement debouncing patterns for rate limiting and performance optimization.
Basic Usage ​
typescript
// Simple debounce function
function debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return function (...args: Parameters<T>) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), wait);
};
}
// Debounce with immediate option
function debounceWithImmediate<T extends (...args: any[]) => void>(
func: T,
wait: number,
immediate: boolean = false
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return function (...args: Parameters<T>) {
const callNow = immediate && !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
timeoutId = null!;
if (!immediate) func(...args);
}, wait);
if (callNow) func(...args);
};
}
Advanced Patterns ​
Promise-based Debounce ​
typescript
interface DebouncedFunction<T extends (...args: any[]) => any> {
(...args: Parameters<T>): Promise<ReturnType<T>>;
cancel: () => void;
flush: () => Promise<ReturnType<T> | undefined>;
}
function debouncePromise<T extends (...args: any[]) => any>(
func: T,
wait: number
): DebouncedFunction<T> {
let timeoutId: NodeJS.Timeout;
let latestResolve: ((value: ReturnType<T>) => void) | null = null;
let latestReject: ((reason: any) => void) | null = null;
let latestArgs: Parameters<T> | null = null;
const debouncedFunction = (
...args: Parameters<T>
): Promise<ReturnType<T>> => {
latestArgs = args;
return new Promise((resolve, reject) => {
latestResolve = resolve;
latestReject = reject;
clearTimeout(timeoutId);
timeoutId = setTimeout(async () => {
try {
const result = await func(...args);
latestResolve!(result);
} catch (error) {
latestReject!(error);
}
}, wait);
});
};
debouncedFunction.cancel = () => {
clearTimeout(timeoutId);
if (latestReject) {
latestReject(new Error('Debounced function cancelled'));
}
};
debouncedFunction.flush = async () => {
if (!latestArgs) return undefined;
clearTimeout(timeoutId);
return func(...latestArgs);
};
return debouncedFunction;
}
Debounce with Queue ​
typescript
interface QueuedDebounceOptions {
wait: number;
maxQueueSize?: number;
onQueueFull?: () => void;
}
class QueuedDebounce<T> {
private queue: T[] = [];
private timeoutId: NodeJS.Timeout | null = null;
private processing = false;
constructor(
private processor: (items: T[]) => Promise<void>,
private options: QueuedDebounceOptions
) {}
add(item: T): void {
if (
this.options.maxQueueSize &&
this.queue.length >= this.options.maxQueueSize
) {
if (this.options.onQueueFull) {
this.options.onQueueFull();
}
return;
}
this.queue.push(item);
this.scheduleProcessing();
}
private scheduleProcessing(): void {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => this.processQueue(), this.options.wait);
}
private async processQueue(): Promise<void> {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const items = [...this.queue];
this.queue = [];
try {
await this.processor(items);
} catch (error) {
console.error('Queue processing error:', error);
// Re-queue failed items
this.queue.unshift(...items);
} finally {
this.processing = false;
if (this.queue.length > 0) {
this.scheduleProcessing();
}
}
}
flush(): Promise<void> {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
return this.processQueue();
}
clear(): void {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
this.queue = [];
}
getQueueSize(): number {
return this.queue.length;
}
isProcessing(): boolean {
return this.processing;
}
}
Adaptive Debounce ​
typescript
interface AdaptiveDebounceOptions {
initialWait: number;
minWait: number;
maxWait: number;
adaptationFactor: number;
sampleSize: number;
}
class AdaptiveDebounce<T extends (...args: any[]) => any> {
private timeoutId: NodeJS.Timeout | null = null;
private currentWait: number;
private executionTimes: number[] = [];
private lastExecutionStart: number = 0;
constructor(
private func: T,
private options: AdaptiveDebounceOptions
) {
this.currentWait = options.initialWait;
}
execute(...args: Parameters<T>): void {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(async () => {
this.lastExecutionStart = Date.now();
try {
await this.func(...args);
this.recordExecutionTime();
} catch (error) {
console.error('Execution error:', error);
this.increaseWait();
}
}, this.currentWait);
}
private recordExecutionTime(): void {
const executionTime = Date.now() - this.lastExecutionStart;
this.executionTimes.push(executionTime);
if (this.executionTimes.length > this.options.sampleSize) {
this.executionTimes.shift();
}
this.adjustWait();
}
private adjustWait(): void {
if (this.executionTimes.length < this.options.sampleSize) {
return;
}
const avgExecutionTime =
this.executionTimes.reduce((a, b) => a + b) / this.executionTimes.length;
if (avgExecutionTime > this.currentWait * 0.8) {
this.increaseWait();
} else if (avgExecutionTime < this.currentWait * 0.2) {
this.decreaseWait();
}
}
private increaseWait(): void {
this.currentWait = Math.min(
this.options.maxWait,
this.currentWait * this.options.adaptationFactor
);
}
private decreaseWait(): void {
this.currentWait = Math.max(
this.options.minWait,
this.currentWait / this.options.adaptationFactor
);
}
cancel(): void {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
getCurrentWait(): number {
return this.currentWait;
}
getAverageExecutionTime(): number {
if (this.executionTimes.length === 0) return 0;
return (
this.executionTimes.reduce((a, b) => a + b) / this.executionTimes.length
);
}
}