What is Asynchronous Programming?
Asynchronous code allows operations to run without blocking other code execution. This is essential for tasks like API calls, timers, and file operations.
🔄 Synchronous vs Asynchronous
// Synchronous (blocking)
console.log('Start');
function slowTask() {
// This blocks everything
let start = Date.now();
while (Date.now() - start < 3000) {}
console.log('Slow task done');
}
slowTask();
console.log('End'); // Has to wait for slowTask
// Output:
// Start
// (3 second delay)
// Slow task done
// End
// Asynchronous (non-blocking)
console.log('Start');
setTimeout(() => {
console.log('Async task done');
}, 3000);
console.log('End'); // Runs immediately
// Output:
// Start
// End
// (3 second delay)
// Async task done
⏲️ setTimeout and setInterval
setTimeout - Delay Execution
// Basic setTimeout
setTimeout(() => {
console.log('Runs after 2 seconds');
}, 2000);
// With parameters
setTimeout((name, age) => {
console.log(`Hello ${name}, age ${age}`);
}, 1000, 'John', 30);
// Store timeout ID
let timeoutId = setTimeout(() => {
console.log('This might not run');
}, 5000);
// Cancel timeout
clearTimeout(timeoutId);
// Immediate execution (0ms still async)
setTimeout(() => {
console.log('Runs after current code');
}, 0);
console.log('Runs first');
// Named function
function greet() {
console.log('Hello');
}
setTimeout(greet, 1000);
setInterval - Repeated Execution
// Basic setInterval
let count = 0;
let intervalId = setInterval(() => {
count++;
console.log('Count:', count);
}, 1000); // Every second
// Stop after condition
let counter = 0;
let interval = setInterval(() => {
counter++;
console.log(counter);
if (counter >= 5) {
clearInterval(interval);
console.log('Stopped');
}
}, 1000);
// Countdown timer
function countdown(seconds) {
console.log(seconds);
let interval = setInterval(() => {
seconds--;
console.log(seconds);
if (seconds <= 0) {
clearInterval(interval);
console.log('Done!');
}
}, 1000);
}
countdown(5);
// Clock example
function clock() {
function updateTime() {
let now = new Date();
console.log(now.toLocaleTimeString());
}
updateTime(); // Show immediately
setInterval(updateTime, 1000);
}
clock();
Timing Accuracy
// Timing is not guaranteed (minimum delay)
setTimeout(() => {
console.log('Expected 100ms');
}, 100);
// Heavy task might delay it
for (let i = 0; i < 1000000000; i++) {}
// setInterval drift problem
let start = Date.now();
let count = 0;
setInterval(() => {
count++;
console.log('Expected:', count * 1000);
console.log('Actual:', Date.now() - start);
}, 1000);
// Better: recursive setTimeout (self-correcting)
function accurateInterval(callback, interval) {
let expected = Date.now() + interval;
function step() {
let drift = Date.now() - expected;
callback();
expected += interval;
setTimeout(step, Math.max(0, interval - drift));
}
setTimeout(step, interval);
}
accurateInterval(() => console.log('Accurate'), 1000);
📞 Callbacks
Callback Pattern
// Function that takes callback
function fetchData(callback) {
setTimeout(() => {
let data = { id: 1, name: 'John' };
callback(data);
}, 1000);
}
// Use callback
fetchData(data => {
console.log('Data received:', data);
});
// Error-first callbacks (Node.js convention)
function fetchDataWithError(callback) {
setTimeout(() => {
let error = null;
let data = { id: 1 };
// Simulated error
if (Math.random() > 0.5) {
error = new Error('Failed to fetch');
data = null;
}
callback(error, data);
}, 1000);
}
// Usage
fetchDataWithError((error, data) => {
if (error) {
console.error('Error:', error.message);
return;
}
console.log('Success:', data);
});
Callback Hell
// Nested callbacks (hard to read/maintain)
function step1(callback) {
setTimeout(() => {
console.log('Step 1');
callback();
}, 1000);
}
function step2(callback) {
setTimeout(() => {
console.log('Step 2');
callback();
}, 1000);
}
function step3(callback) {
setTimeout(() => {
console.log('Step 3');
callback();
}, 1000);
}
// Pyramid of doom
step1(() => {
step2(() => {
step3(() => {
console.log('All done');
});
});
});
// Real-world example (worse)
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
getLikes(comments[0].id, (likes) => {
console.log('Finally got likes');
});
});
});
});
🎯 Event Loop
// Understanding execution order
console.log('1. Synchronous');
setTimeout(() => {
console.log('4. Timeout (macro task)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise (micro task)');
});
console.log('2. Synchronous');
// Output:
// 1. Synchronous
// 2. Synchronous
// 3. Promise (micro task)
// 4. Timeout (macro task)
// Execution phases:
// 1. Synchronous code runs first
// 2. Micro tasks (Promises) run next
// 3. Macro tasks (setTimeout, setInterval) run last
// Complex example
console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
setTimeout(() => console.log('Timeout 2'), 0);
Promise.resolve().then(() => {
console.log('Promise 3');
setTimeout(() => console.log('Timeout 3'), 0);
});
console.log('End');
// Output:
// Start
// End
// Promise 1
// Promise 2
// Promise 3
// Timeout 1
// Timeout 2
// Timeout 3
🔄 Practical Async Patterns
Debouncing
// Delay execution until pause in events
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage: search input
let searchInput = document.querySelector('#search');
let search = debounce((query) => {
console.log('Searching for:', query);
// Make API call
}, 500);
searchInput.addEventListener('input', e => {
search(e.target.value);
});
// Typing "hello" logs once after 500ms pause, not 5 times
Throttling
// Limit execution frequency
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage: scroll event
let handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 1000);
window.addEventListener('scroll', handleScroll);
// Logs at most once per second while scrolling
Polling
// Check status repeatedly
function poll(fn, interval, maxAttempts) {
let attempts = 0;
let intervalId = setInterval(() => {
attempts++;
let result = fn();
if (result || attempts >= maxAttempts) {
clearInterval(intervalId);
if (result) {
console.log('Success');
} else {
console.log('Max attempts reached');
}
}
}, interval);
}
// Usage: check if element exists
poll(() => {
let element = document.querySelector('.dynamic-content');
return element !== null;
}, 100, 50); // Check every 100ms, max 50 times
// Better with callbacks
function pollWithCallback(fn, interval, maxAttempts, callback) {
let attempts = 0;
let intervalId = setInterval(() => {
attempts++;
let result = fn();
if (result) {
clearInterval(intervalId);
callback(null, result);
} else if (attempts >= maxAttempts) {
clearInterval(intervalId);
callback(new Error('Max attempts'), null);
}
}, interval);
}
pollWithCallback(
() => document.querySelector('.target'),
100,
50,
(error, element) => {
if (error) {
console.error('Not found');
} else {
console.log('Found:', element);
}
}
);
Retry Logic
// Retry failed operations
function retry(fn, maxRetries, delay) {
let attempts = 0;
function attempt() {
fn(
// Success callback
(result) => {
console.log('Success:', result);
},
// Error callback
(error) => {
attempts++;
if (attempts < maxRetries) {
console.log(`Retry ${attempts}/${maxRetries}`);
setTimeout(attempt, delay);
} else {
console.error('All retries failed:', error);
}
}
);
}
attempt();
}
// Usage
function flakeyOperation(onSuccess, onError) {
setTimeout(() => {
if (Math.random() > 0.7) {
onSuccess('Data loaded');
} else {
onError(new Error('Failed'));
}
}, 1000);
}
retry(flakeyOperation, 3, 1000);
Rate Limiting
// Limit requests per time period
function createRateLimiter(maxRequests, perMilliseconds) {
let requests = [];
return function(fn) {
let now = Date.now();
// Remove old requests
requests = requests.filter(time => now - time < perMilliseconds);
if (requests.length < maxRequests) {
requests.push(now);
fn();
return true;
} else {
console.log('Rate limit exceeded');
return false;
}
};
}
// Usage: max 5 requests per 10 seconds
let limiter = createRateLimiter(5, 10000);
document.querySelector('#apiButton').addEventListener('click', () => {
limiter(() => {
console.log('API call made');
// Make actual API call
});
});
💡 Practical Examples
Auto-save
let textarea = document.querySelector('textarea');
let saveStatus = document.querySelector('#saveStatus');
let autoSave = debounce(() => {
saveStatus.textContent = 'Saving...';
// Simulate save
setTimeout(() => {
localStorage.setItem('draft', textarea.value);
saveStatus.textContent = 'Saved ✓';
setTimeout(() => {
saveStatus.textContent = '';
}, 2000);
}, 500);
}, 1000);
textarea.addEventListener('input', autoSave);
// Load draft on page load
document.addEventListener('DOMContentLoaded', () => {
let draft = localStorage.getItem('draft');
if (draft) {
textarea.value = draft;
}
});
Progress Bar
function simulateProgress(callback) {
let progress = 0;
let progressBar = document.querySelector('.progress');
let interval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
callback();
}
progressBar.style.width = progress + '%';
progressBar.textContent = Math.floor(progress) + '%';
}, 200);
}
// Usage
let button = document.querySelector('#startBtn');
button.addEventListener('click', () => {
simulateProgress(() => {
console.log('Complete!');
alert('Process finished');
});
});
Delayed Actions
// Show notification for duration
function showNotification(message, duration) {
let notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 10); // Small delay for animation
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
notification.remove();
}, 300); // Wait for fade out animation
}, duration);
}
showNotification('Saved successfully', 3000);
// Undo functionality
let undoTimeout;
function deleteItem(item, callback) {
item.classList.add('deleted');
showNotification('Item deleted. Undo?', 5000);
undoTimeout = setTimeout(() => {
item.remove();
callback();
}, 5000);
}
function undo(item) {
clearTimeout(undoTimeout);
item.classList.remove('deleted');
showNotification('Undo successful', 2000);
}
Type Ahead Search
let searchInput = document.querySelector('#typeahead');
let suggestions = document.querySelector('#suggestions');
let currentRequest = 0;
let performSearch = debounce((query) => {
if (!query.trim()) {
suggestions.innerHTML = '';
return;
}
// Track request order
let requestId = ++currentRequest;
// Simulate API call
setTimeout(() => {
// Ignore if newer request made
if (requestId !== currentRequest) return;
let results = mockSearch(query);
displayResults(results);
}, 500);
}, 300);
searchInput.addEventListener('input', e => {
performSearch(e.target.value);
});
function mockSearch(query) {
let data = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
return data.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
}
function displayResults(results) {
suggestions.innerHTML = results
.map(item => `${item}`)
.join('');
}
Lazy Loading
// Load content when scrolled into view
function lazyLoad(selector, callback) {
let elements = document.querySelectorAll(selector);
let observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
callback(entry.target);
observer.unobserve(entry.target);
}
});
});
elements.forEach(el => observer.observe(el));
}
// Usage: load images
lazyLoad('[data-src]', (img) => {
// Simulate loading delay
setTimeout(() => {
img.src = img.dataset.src;
img.classList.add('loaded');
}, 500);
});
// Usage: load content sections
lazyLoad('.lazy-section', (section) => {
setTimeout(() => {
let content = section.dataset.content;
section.innerHTML = `${content}
`;
section.classList.add('loaded');
}, 300);
});
🎯 Key Takeaways
- Asynchronous: Non-blocking operations that don't halt other code
- setTimeout: Execute code once after delay, returns ID for cancellation
- setInterval: Execute code repeatedly, use clearInterval to stop
- Callbacks: Functions passed to async operations, can nest (callback hell)
- Event Loop: Synchronous → Micro tasks (Promises) → Macro tasks (timers)
- Debouncing: Delay until events pause (search input)
- Throttling: Limit execution frequency (scroll events)
- 0ms Timeout: Still asynchronous, runs after current code completes