본문으로 건너뛰기

throttle

Creates a throttled function that limits execution frequency by enforcing a minimum interval. Implements throttling that ensures function execution occurs at most once per specified time window. Unlike debouncing which delays execution, throttling enforces a rate limit by executing immediately (leading) and/or at the end of each time window (trailing) when calls are made. Perfect for performance-critical scenarios like scroll/resize handlers or API rate limiting.

Signature

const throttle: <F extends Fn<any[]>>(fn: F, ms: number, { signal, leading, trailing }?: ExecutionOptions) => ThrottledFn<F>

Parameters

NameTypeDescription
fn-The function to throttle
ms-Minimum interval between executions in milliseconds
options-Configuration options for throttling behavior

Returns

Enhanced throttled function with control methods

Examples

Basic scroll handler throttling

import { throttle } from '@winglet/common-utils';

const handleScroll = () => {
console.log('Scroll position:', window.scrollY);
updateScrollIndicator();
};

const throttledScroll = throttle(handleScroll, 100);

// In scroll event handler
window.addEventListener('scroll', throttledScroll);
// Handler executes at most once every 100ms during scrolling

API rate limiting

const sendAnalytics = async (event: AnalyticsEvent) => {
console.log('Sending analytics:', event);
await fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify(event)
});
};

const throttledAnalytics = throttle(sendAnalytics, 1000);

// Multiple rapid calls - only sent once per second
button.addEventListener('click', () => {
throttledAnalytics({ type: 'button_click', timestamp: Date.now() });
});

Execution mode configurations

// Leading only: Execute immediately, ignore subsequent calls in window
const leadingOnly = throttle(updateCounter, 500, {
leading: true,
trailing: false
});

// Trailing only: Execute once at end of each time window
const trailingOnly = throttle(saveProgress, 1000, {
leading: false,
trailing: true
});

// Both modes (default): Execute immediately and at end if calls continue
const bothModes = throttle(refreshData, 2000); // Both leading and trailing true

Manual control and cleanup

const throttledFunction = throttle(expensiveUpdate, 200);

// Normal usage - respects throttle timing
throttledFunction(data1);
throttledFunction(data2); // May be ignored if within time window

// Force immediate execution (bypasses throttle)
throttledFunction.execute(); // Executes with last arguments immediately

// Cancel any pending trailing execution
throttledFunction.clear(); // Stops scheduled execution

// Cleanup on component unmount
useEffect(() => {
return () => throttledFunction.clear();
}, []);

AbortSignal integration

const controller = new AbortController();

const throttledFetch = throttle(
async (url: string) => {
const response = await fetch(url);
return response.json();
},
500,
{ signal: controller.signal }
);

// Use normally
throttledFetch('/api/data');

// Abort all pending operations
controller.abort(); // Prevents further executions

Mouse move tracking with throttling

const trackMousePosition = (event: MouseEvent) => {
console.log(`Mouse at: ${event.clientX}, ${event.clientY}`);
updateCursor(event.clientX, event.clientY);
};

const throttledMouseTracker = throttle(trackMousePosition, 16); // ~60fps

document.addEventListener('mousemove', throttledMouseTracker);

// Cleanup
document.removeEventListener('mousemove', throttledMouseTracker);
throttledMouseTracker.clear();

Network request throttling

const searchAPI = async (query: string) => {
if (!query.trim()) return;
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
return response.json();
};

// Limit search requests to once per 300ms
const throttledSearch = throttle(searchAPI, 300, {
leading: false, // Don't search immediately on first keystroke
trailing: true // Search after user activity settles
});

searchInput.addEventListener('input', (e) => {
throttledSearch(e.target.value);
});

Playground

import { throttle } from '@winglet/common-utils';

const handleScroll = () => {
console.log('Scroll position:', window.scrollY);
updateScrollIndicator();
};

const throttledScroll = throttle(handleScroll, 100);

// In scroll event handler
window.addEventListener('scroll', throttledScroll);
// Handler executes at most once every 100ms during scrolling

Notes

Execution Timing:

  • Leading: Function executes immediately when called (if not in cooldown)
  • Trailing: Function executes at the end of time window (if calls occurred during window)
  • Time Window: Period during which subsequent calls are limited/ignored

Throttle vs Debounce:

  • Throttle: Guarantees execution at regular intervals during activity
  • Debounce: Delays execution until activity stops
  • Use Throttle For: Scroll handlers, mouse tracking, progress updates
  • Use Debounce For: Search inputs, form validation, resize handlers

Performance Benefits & Optimization Guidelines:

  • Call Frequency Control: Limits execution to 1 call per specified interval (e.g., 60fps = 16.67ms)
  • Memory Efficiency: ~120 bytes overhead per throttled function
  • CPU Optimization: Date.now() comparison adds <0.1ms overhead per call
  • UI Responsiveness: Maintains smooth animations by preventing frame drops
  • Network Optimization: Reduces API calls by 80-95% in continuous interaction scenarios
  • Battery Life: Reduces mobile device battery drain by limiting excessive computations

Frame Rate Guidelines:

// 60fps animations (recommended for smooth UI)
const smoothAnimation = throttle(updatePosition, 16); // ~60fps

// 30fps for less critical updates (good performance/battery balance)
const moderateUpdate = throttle(updateStats, 33); // ~30fps

// 10fps for background tasks (minimal performance impact)
const backgroundSync = throttle(syncData, 100); // ~10fps

// API rate limiting (respect server limits)
const apiThrottle = throttle(searchAPI, 300); // Max 3.33 requests/second

Common Patterns:

  • Scroll/Resize: Leading + trailing for immediate response + final update
  • Button Clicks: Leading only to prevent double-clicks
  • API Calls: Trailing only to batch requests
  • Animation: Leading + trailing for smooth visual feedback