Master the fundamentals of async programming in JavaScript - Essential knowledge for building responsive web applications.
- 🎯 Overview
- ⚡ JavaScript's Fundamental Nature
- 🔄 Achieving Asynchronous Behavior
- ⏰ Timeouts and Intervals
- 📞 Callbacks
- 🤝 Promises
- ⏳ Async/Await
- 🔄 The Event Loop
- 📚 Additional Resources
Asynchronous JavaScript enables non-blocking operations, allowing applications to remain responsive while performing time-consuming tasks like network requests, file operations, or database queries.
- Non-blocking: UI stays responsive during async operations
- Concurrent execution: Multiple operations can run simultaneously
- Better user experience: No frozen interfaces during data loading
JavaScript is inherently synchronous, blocking, and single-threaded:
- Synchronous: Code executes line by line, one at a time
- Blocking: Long operations freeze the entire application
- Single-threaded: Only one operation can run at any moment
// This blocks everything for 5 seconds
function fetchData() {
const start = Date.now();
while (Date.now() - start < 5000) {
// Busy waiting - blocks the main thread
}
return "Data";
}
When synchronous operations block the main thread:
- ⏰ Timers are delayed
- 🖱️ User interactions are ignored
- 🔄 Event Loop cannot process queued tasks
- ❌ Application becomes unresponsive
JavaScript relies on Web APIs (browser) or Node.js APIs to achieve asynchronicity.
setTimeout
/setInterval
- Timer-based operationsfetch
- Network requests- DOM Events - User interactions
- File API - File operations
Synchronous Code → Web API → Background Processing → Callback Queue → Event Loop → Call Stack
Executes a function once after a specified delay.
Event Loop Behavior:
setTimeout
is pushed to Call Stack- Timer is handed to Web API (background)
- Call Stack continues with other code
- When timer expires, callback goes to Task Queue
- Event Loop moves callback to Call Stack when empty
Repeatedly executes a function at specified intervals.
Important Notes:
⚠️ Minimum delay, not guaranteed - Execution may be delayed if Call Stack is busy- ✅ Recursive setTimeout preferred - Guarantees timing between executions
- ❌ setInterval can overlap - If execution takes longer than interval
console.log("1️⃣ Start");
setTimeout(() => console.log("⏰ Timer"), 0);
console.log("2️⃣ End");
// Output: 1️⃣ Start → 2️⃣ End → ⏰ Timer
// Event Loop: Call Stack → Task Queue → Call Stack
Functions passed as arguments to other functions, executed when a specific event occurs.
- Execute immediately when called
- Examples: Array methods (
map
,filter
,reduce
) - No Event Loop involvement
- Execute after a delay or event
- Examples:
setTimeout
, event handlers, API calls - Managed by Event Loop
fetchUser(userId, (user) => {
fetchPosts(user.id, (posts) => {
fetchComments(posts[0].id, (comments) => {
// Nested callbacks become hard to read
});
});
});
- Each async callback goes to appropriate queue
- Callback Hell creates complex queue management
- Error handling becomes difficult across nested levels
A cleaner way to handle asynchronous operations with better error handling and chaining.
- Pending - Initial state, neither fulfilled nor rejected
- Fulfilled - Operation completed successfully
- Rejected - Operation failed
- Chainable -
.then()
and.catch()
return new promises - Error handling - Centralized error management
- Static methods -
Promise.all()
,Promise.race()
,Promise.allSettled()
console.log("1️⃣ Start");
Promise.resolve().then(() => console.log("🤝 Promise"));
setTimeout(() => console.log("⏰ Timer"), 0);
console.log("2️⃣ End");
// Output: 1️⃣ Start → 2️⃣ End → 🤝 Promise → ⏰ Timer
// Event Loop Priority: Microtask Queue > Task Queue
- Promises go to Microtask Queue (higher priority)
- setTimeout goes to Task Queue (lower priority)
- Event Loop processes Microtasks before Tasks
Syntactic sugar over promises, making asynchronous code look synchronous.
- async functions always return promises
- await pauses execution until promise settles
- try/catch provides clean error handling
- Sequential vs Concurrent execution patterns
async function sequential() {
const result1 = await apiCall1(); // Waits 2s
const result2 = await apiCall2(); // Waits 1s
// Total time: 3 seconds
}
async function concurrent() {
const [result1, result2] = await Promise.all([
apiCall1(), // 2s
apiCall2() // 1s
]);
// Total time: 2 seconds (longest request)
}
- await creates microtasks
- Async functions don't block the main thread
- Event Loop continues processing other tasks during await
The heart of JavaScript's asynchronous behavior, managing the single-threaded execution model.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ JavaScript Runtime Environment │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Memory Heap │ │ Call Stack │ │ Web APIs │ │
│ │ │ │ │ │ │ │
│ │ • Variables │ │ • Functions │ │ • setTimeout │ │
│ │ • Objects │ │ • Execution │ │ • setInterval │ │
│ │ • Functions │ │ • LIFO Order │ │ • fetch │ │
│ │ • Closures │ │ • One at a time │ │ • DOM Events │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Microtask Queue │ │ Task Queue │ │ Event Loop │ │
│ │ │ │ │ │ │ │
│ │ • Promise .then │ │ • setTimeout │ │ • Orchestrator │ │
│ │ • Promise .catch│ │ • setInterval │ │ • Priority: │ │
│ │ • async/await │ │ • DOM Events │ │ Microtask > │ │
│ │ • queueMicrotask│ │ • User Events │ │ Task Queue │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
while (true) {
if (callStack.isEmpty()) {
if (microtaskQueue.hasItems()) {
// Process all microtasks (higher priority)
const task = microtaskQueue.dequeue();
callStack.push(task);
} else if (taskQueue.hasItems()) {
// Process one task (lower priority)
const task = taskQueue.dequeue();
callStack.push(task);
}
}
}
- Synchronous Code (Call Stack)
- Microtask Queue (Promises, async/await)
- Task Queue (setTimeout, setInterval, events)
javascript
// Interactive demonstration
console.log("🎬 Event Loop Demo Starting...");
// Phase 1: Synchronous execution
console.log("📊 Phase 1: Synchronous code starts");
// Phase 2: Async operations scheduled
setTimeout(() => {
console.log("⏰ Phase 3: Task Queue (setTimeout)");
}, 0);
Promise.resolve().then(() => {
console.log("🤝 Phase 2: Microtask Queue (Promise)");
});
console.log("📊 Phase 1: Synchronous code ends");
// Expected execution order:
// 📊 Phase 1: Synchronous code starts
// 📊 Phase 1: Synchronous code ends
// 🤝 Phase 2: Microtask Queue (Promise)
// ⏰ Phase 3: Task Queue (setTimeout)
- Single-threaded: JavaScript can only do one thing at a time
- Non-blocking: Async operations don't freeze the application
- Event-driven: Code responds to events and timers
- Queue-based: Tasks wait in queues until Call Stack is empty
- Priority system: Microtasks have higher priority than tasks