Docs
18.5-Timers-and-Scheduling
11.5 Timers and Scheduling
Overview
JavaScript provides several mechanisms for scheduling code execution: setTimeout, setInterval, requestAnimationFrame, and requestIdleCallback. Understanding these is essential for creating responsive and performant applications.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Timer Methods Overview β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β setTimeout(fn, delay) β
β βββββββββββββββββββββ β
β Execute ONCE after delay (milliseconds) β
β β
β Time ββββββββββββββββββββββββββββββββββββββΆ β
β |βββ delay βββ| β
β βββββββββββββββ β
β β waiting β β fn() called β
β βββββββββββββββ β
β β
β setInterval(fn, interval) β
β βββββββββββββββββββββββββ β
β Execute REPEATEDLY at interval β
β β
β Time ββββββββββββββββββββββββββββββββββββββΆ β
β |ββ int ββ|ββ int ββ|ββ int ββ| β
β fn() fn() fn() β
β β
β requestAnimationFrame(fn) β
β βββββββββββββββββββββββββ β
β Execute before next repaint (~60fps = ~16.67ms) β
β β
β requestIdleCallback(fn) β
β βββββββββββββββββββββββ β
β Execute when browser is idle β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
setTimeout
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β setTimeout β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Syntax: β
β βββββββ β
β const timerId = setTimeout(callback, delay, ...args) β
β β
β β’ callback: Function to execute β
β β’ delay: Time in milliseconds (default: 0) β
β β’ args: Arguments passed to callback β
β β’ Returns: Timer ID for cancellation β
β β
β Cancellation: β
β βββββββββββββ β
β clearTimeout(timerId) β
β β
β Examples: β
β βββββββββ β
β // Basic usage β
β setTimeout(() => console.log('Hello'), 1000); β
β β
β // With arguments β
β setTimeout((name) => console.log(`Hi ${name}`), 1000, 'John'); β
β β
β // Cancel before execution β
β const id = setTimeout(() => console.log('Never'), 5000); β
β clearTimeout(id); β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
setInterval
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β setInterval β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Syntax: β
β βββββββ β
β const intervalId = setInterval(callback, interval, ...args) β
β β
β Cancellation: β
β βββββββββββββ β
β clearInterval(intervalId) β
β β
β Example: β
β βββββββββ β
β let count = 0; β
β const id = setInterval(() => { β
β count++; β
β console.log(`Count: ${count}`); β
β if (count >= 5) clearInterval(id); β
β }, 1000); β
β β
β β οΈ Warning: Interval drift β
β ββββββββββββββββββββββββ β
β If callback takes longer than interval, β
β calls can stack up or be delayed. β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
setTimeout with 0 Delay
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Zero Delay setTimeout β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β setTimeout(fn, 0) doesn't run immediately! β
β β
β console.log('1'); β
β setTimeout(() => console.log('2'), 0); β
β console.log('3'); β
β β
β Output: 1, 3, 2 β
β β
β Why? Event Loop: β
β βββββββββββββββββ β
β 1. Synchronous code runs first (Call Stack) β
β 2. setTimeout callback goes to Task Queue β
β 3. Event Loop picks up callback after stack is empty β
β β
β Minimum delay is ~4ms in browsers (after 5 nested calls) β
β β
β Use Cases: β
β ββββββββββ β
β β’ Break up long-running tasks β
β β’ Allow DOM updates to render β
β β’ Defer execution to end of event loop tick β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
requestAnimationFrame
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β requestAnimationFrame β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Syntax: β
β βββββββ β
β const frameId = requestAnimationFrame(callback) β
β cancelAnimationFrame(frameId) β
β β
β Callback receives: DOMHighResTimeStamp (ms since page load) β
β β
β Why use it? β
β ββββββββββββ β
β β Synced with display refresh rate (usually 60fps) β
β β Paused in background tabs (saves battery/CPU) β
β β Optimized by browser for smooth animations β
β β More accurate than setInterval for animations β
β β
β Animation Loop Pattern: β
β βββββββββββββββββββββββ β
β function animate(timestamp) { β
β // Update animation state β
β element.style.left = `${x}px`; β
β β
β // Continue animation β
β requestAnimationFrame(animate); β
β } β
β requestAnimationFrame(animate); β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
requestIdleCallback
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β requestIdleCallback β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Execute code when browser is idle (not critical work) β
β β
β Syntax: β
β βββββββ β
β requestIdleCallback(callback, { timeout: 2000 }) β
β cancelIdleCallback(id) β
β β
β Callback receives IdleDeadline object: β
β βββββββββββββββββββββββββββββββββββββ β
β deadline.didTimeout β true if timeout was reached β
β deadline.timeRemaining() β ms left in idle period β
β β
β Example: β
β βββββββββ β
β requestIdleCallback((deadline) => { β
β while (deadline.timeRemaining() > 0 && tasks.length) { β
β const task = tasks.shift(); β
β processTask(task); β
β } β
β β
β if (tasks.length > 0) { β
β requestIdleCallback(processTasks); β
β } β
β }, { timeout: 1000 }); β
β β
β β οΈ Not supported in Safari - use polyfill or fallback β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Debounce
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Debounce β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Wait for pause in events before executing β
β β
β Events: βxβxβxβxβxβββββββββββββxβxβxβββββββββ β
β |ββ wait ββ| |β waitβ| β
β Calls: βββββββββββββfn()βββββββββββfn() β
β β
β Use Cases: β
β ββββββββββ β
β β’ Search input (wait for user to stop typing) β
β β’ Window resize handling β
β β’ Button click protection β
β β
β Implementation: β
β βββββββββββββββ β
β function debounce(fn, delay) { β
β let timeoutId; β
β return function(...args) { β
β clearTimeout(timeoutId); β
β timeoutId = setTimeout(() => fn.apply(this, args), β
β delay); β
β }; β
β } β
β β
β Usage: β
β ββββββ β
β const debouncedSearch = debounce(search, 300); β
β input.addEventListener('input', debouncedSearch); β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Throttle
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Throttle β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Execute at most once per time period β
β β
β Events: βxβxβxβxβxβxβxβxβxβxβxβxβxβ β
β |ββ limit ββ|ββ limit ββ| β
β Calls: fn()ββββββββfn()ββββββββfn() β
β β
β Use Cases: β
β ββββββββββ β
β β’ Scroll event handling β
β β’ Mouse move tracking β
β β’ API rate limiting β
β β’ Game input handling β
β β
β Implementation: β
β βββββββββββββββ β
β function throttle(fn, limit) { β
β let waiting = false; β
β return function(...args) { β
β if (waiting) return; β
β fn.apply(this, args); β
β waiting = true; β
β setTimeout(() => waiting = false, limit); β
β }; β
β } β
β β
β Usage: β
β ββββββ β
β const throttledScroll = throttle(handleScroll, 100); β
β window.addEventListener('scroll', throttledScroll); β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Debounce vs Throttle
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Debounce vs Throttle β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Debounce Throttle β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β When After pause At regular intervals β
β Guarantee 1 call after events Max 1 call per period β
β Delay From last event From first event β
β β
β Example Timeline (300ms): β
β ββββββββββββββββββββββββββ β
β Events: 100 200 300 400 500 βββββββ 1000 β
β Debounce: βββββββββββββββββββββββββββ 800 (calls once) β
β Throttle: 100 ββββ 400 ββββ 700 ββββ 1000 (calls 4x) β
β β
β Choose Debounce: β
β ββββββββββββββββ β
β β’ Final value matters (search, resize) β
β β’ Want to wait for "settling" β
β β’ Reducing API calls β
β β
β Choose Throttle: β
β ββββββββββββββββ β
β β’ Regular updates needed (scroll position) β
β β’ Real-time feedback (game input) β
β β’ Rate limiting β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Timer Accuracy
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Timer Accuracy β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Timers are NOT guaranteed to be precise! β
β β
β Factors affecting accuracy: β
β βββββββββββββββββββββββββββ β
β β’ Minimum delay (~4ms in browsers) β
β β’ Browser throttling in background tabs β
β β’ System load and available resources β
β β’ Other JavaScript execution blocking β
β β’ Event loop congestion β
β β
β Background Tab Throttling: β
β ββββββββββββββββββββββββββ β
β β’ setTimeout/setInterval: Limited to 1 call/second β
β β’ requestAnimationFrame: Paused completely β
β β’ requestIdleCallback: May not run β
β β
β For precise timing: β
β βββββββββββββββββββ β
β β’ Use performance.now() for measurements β
β β’ Web Workers for background processing β
β β’ Web Audio API for audio timing β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Async Sleep / Delay
// Promise-based delay
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Usage with async/await
async function example() {
console.log('Start');
await delay(2000);
console.log('After 2 seconds');
}
// Cancellable delay
function cancellableDelay(ms) {
let timeoutId;
const promise = new Promise((resolve, reject) => {
timeoutId = setTimeout(resolve, ms);
});
return {
promise,
cancel: () => clearTimeout(timeoutId),
};
}
Common Patterns
Polling
async function poll(fn, interval, maxAttempts = Infinity) {
let attempts = 0;
while (attempts < maxAttempts) {
const result = await fn();
if (result) return result;
attempts++;
await delay(interval);
}
throw new Error('Polling timeout');
}
// Usage
await poll(
async () => {
const status = await checkStatus();
return status === 'complete' ? status : null;
},
1000,
30
);
Retry with Backoff
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await delay(baseDelay * Math.pow(2, i));
}
}
}
Best Practices
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Best Practices β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. Always store timer IDs β
β const id = setTimeout(...); β
β // Later: clearTimeout(id); β
β β
β 2. Clean up on component unmount β
β useEffect(() => { β
β const id = setInterval(...); β
β return () => clearInterval(id); β
β }, []); β
β β
β 3. Use requestAnimationFrame for visual updates β
β // Not: setInterval(() => moveElement(), 16); β
β // Use: requestAnimationFrame(animate); β
β β
β 4. Debounce/throttle event handlers β
β window.addEventListener('resize', debounce(handle, 250)); β
β β
β 5. Use requestIdleCallback for non-critical work β
β requestIdleCallback(() => trackAnalytics()); β
β β
β 6. Consider Web Workers for heavy computation β
β // Timers in workers aren't throttled β
β β
β 7. Use performance.now() for accurate timing β
β const start = performance.now(); β
β // ... work ... β
β const elapsed = performance.now() - start; β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Browser Compatibility
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| setTimeout | 1+ | 1+ | 1+ | 12+ |
| setInterval | 1+ | 1+ | 1+ | 12+ |
| requestAnimationFrame | 24+ | 23+ | 6.1+ | 12+ |
| requestIdleCallback | 47+ | 55+ | β | 79+ |
| performance.now() | 24+ | 15+ | 8+ | 12+ |
Key Takeaways
- β’setTimeout - One-time delayed execution
- β’setInterval - Repeated execution at intervals
- β’requestAnimationFrame - Smooth animations synced with display
- β’requestIdleCallback - Non-critical background tasks
- β’Debounce - Wait for pause before executing
- β’Throttle - Limit execution frequency
- β’Clean up timers - Always cancel when no longer needed
- β’Timers aren't precise - Account for delays and throttling