⏰
Asynchronous JavaScript
Master callbacks, promises, and async/await
Callbacks
Understanding Callbacks
// Basic Callback Example function fetchData(callback) { setTimeout(() => { const data = { id: 1, name: "John Doe" }; callback(null, data); // First parameter is error, second is data }, 1000); } fetchData((error, data) => { if (error) { console.error("Error:", error); } else { console.log("Data received:", data); } }); // Callback Hell Example function getUserData(userId, callback) { fetchUser(userId, (err, user) => { if (err) return callback(err); fetchUserPosts(user.id, (err, posts) => { if (err) return callback(err); fetchPostComments(posts[0].id, (err, comments) => { if (err) return callback(err); callback(null, { user, posts, comments }); }); }); }); } // Error-First Callback Pattern function readFile(filename, callback) { // Simulate file reading setTimeout(() => { if (filename.endsWith('.txt')) { callback(null, "File content here"); } else { callback(new Error("Invalid file type")); } }, 500); }
⚠️ Callback Hell Problems
- • Deeply nested code that's hard to read
- • Difficult error handling
- • Hard to maintain and debug
- • Inversion of control issues
Promises
Promise Basics
// Creating a Promise function fetchUserData(userId) { return new Promise((resolve, reject) => { setTimeout(() => { if (userId > 0) { resolve({ id: userId, name: `User ${userId}` }); } else { reject(new Error("Invalid user ID")); } }, 1000); }); } // Using Promises fetchUserData(1) .then(user => { console.log("User:", user); return fetchUserPosts(user.id); }) .then(posts => { console.log("Posts:", posts); return fetchPostComments(posts[0].id); }) .then(comments => { console.log("Comments:", comments); }) .catch(error => { console.error("Error:", error); }) .finally(() => { console.log("Operation completed"); }); // Promise.all - Wait for all promises const promises = [ fetchUserData(1), fetchUserData(2), fetchUserData(3) ]; Promise.all(promises) .then(users => { console.log("All users:", users); }) .catch(error => { console.error("One or more requests failed:", error); }); // Promise.allSettled - Wait for all, regardless of outcome Promise.allSettled(promises) .then(results => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`User ${index + 1}:`, result.value); } else { console.error(`User ${index + 1} failed:`, result.reason); } }); }); // Promise.race - First to complete wins Promise.race([ fetchUserData(1), new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 2000) ) ]) .then(result => console.log("First result:", result)) .catch(error => console.error("Error or timeout:", error));
Async/Await
Modern Async Syntax
// Basic Async/Await async function getUserData(userId) { try { const user = await fetchUserData(userId); const posts = await fetchUserPosts(user.id); const comments = await fetchPostComments(posts[0].id); return { user, posts, comments }; } catch (error) { console.error("Error fetching user data:", error); throw error; // Re-throw if needed } } // Using the async function async function main() { try { const userData = await getUserData(1); console.log("Complete user data:", userData); } catch (error) { console.error("Failed to get user data:", error); } } // Parallel Execution with Async/Await async function fetchMultipleUsers() { try { // Sequential (slower) const user1 = await fetchUserData(1); const user2 = await fetchUserData(2); const user3 = await fetchUserData(3); // Parallel (faster) const [user1, user2, user3] = await Promise.all([ fetchUserData(1), fetchUserData(2), fetchUserData(3) ]); return [user1, user2, user3]; } catch (error) { console.error("Error fetching users:", error); throw error; } } // Async/Await with Error Handling async function robustApiCall() { const maxRetries = 3; let retries = 0; while (retries < maxRetries) { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { retries++; if (retries === maxRetries) { throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`); } // Wait before retrying await new Promise(resolve => setTimeout(resolve, 1000 * retries)); } } } // Async Generators async function* fetchUsersPaginated() { let page = 1; let hasMore = true; while (hasMore) { const response = await fetch(`/api/users?page=${page}`); const data = await response.json(); yield data.users; hasMore = data.hasMore; page++; } } // Using async generator async function processAllUsers() { for await (const userBatch of fetchUsersPaginated()) { console.log(`Processing ${userBatch.length} users`); // Process each batch } }
Fetch API
Modern HTTP Requests
// Basic GET Request async function fetchUsers() { try { const response = await fetch('/api/users'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const users = await response.json(); return users; } catch (error) { console.error('Fetch error:', error); throw error; } } // POST Request with JSON async function createUser(userData) { try { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getAuthToken()}` }, body: JSON.stringify(userData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Failed to create user'); } return await response.json(); } catch (error) { console.error('Error creating user:', error); throw error; } } // File Upload async function uploadFile(file) { const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/api/upload', { method: 'POST', body: formData // Don't set Content-Type header for FormData }); if (!response.ok) { throw new Error('Upload failed'); } return await response.json(); } catch (error) { console.error('Upload error:', error); throw error; } } // Request with Timeout async function fetchWithTimeout(url, options = {}, timeout = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('Request timeout'); } throw error; } } // Retry Logic async function fetchWithRetry(url, options = {}, maxRetries = 3) { for (let i = 0; i <= maxRetries; i++) { try { const response = await fetch(url, options); if (response.ok) { return response; } // Don't retry on client errors (4xx) if (response.status >= 400 && response.status < 500) { throw new Error(`Client error: ${response.status}`); } if (i === maxRetries) { throw new Error(`Server error: ${response.status}`); } } catch (error) { if (i === maxRetries) { throw error; } // Exponential backoff await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000) ); } } }
Understanding the Event Loop
💡 How JavaScript Handles Async
Call Stack: Where function calls are executed
Web APIs: Browser APIs (setTimeout, fetch, DOM events)
Callback Queue: Where callbacks wait to be executed
Event Loop: Moves callbacks from queue to stack when stack is empty
Event Loop Example
console.log('1'); // Synchronous setTimeout(() => { console.log('2'); // Macro task }, 0); Promise.resolve().then(() => { console.log('3'); // Micro task }); console.log('4'); // Synchronous // Output: 1, 4, 3, 2 // Explanation: // 1. Synchronous code runs first (1, 4) // 2. Micro tasks (Promises) run before macro tasks (setTimeout) // 3. So Promise callback (3) runs before setTimeout callback (2) // Micro tasks vs Macro tasks setTimeout(() => console.log('macro 1'), 0); setTimeout(() => console.log('macro 2'), 0); Promise.resolve().then(() => console.log('micro 1')); Promise.resolve().then(() => console.log('micro 2')); // Output: micro 1, micro 2, macro 1, macro 2
Async Best Practices
✅ Do
- • Use async/await for cleaner code
- • Handle errors with try/catch
- • Use Promise.all for parallel execution
- • Implement proper error boundaries
- • Use AbortController for cancellation
❌ Don't
- • Forget to handle promise rejections
- • Use async/await in loops unnecessarily
- • Mix callbacks and promises
- • Ignore network timeouts
- • Block the main thread with heavy operations