Async/Await
Async/await provides a more readable and intuitive way to work with asynchronous code, making it look and behave more like synchronous code.
Basic Async/Await Syntax
Async Functions
javascript// Function declaration async function fetchData() { return 'Data fetched'; } // Function expression const fetchDataExpr = async function() { return 'Data fetched'; }; // Arrow function const fetchDataArrow = async () => { return 'Data fetched'; }; // Method in object const api = { async getData() { return 'API data'; } }; // Method in class class DataService { async fetchUser(id) { return { id, name: `User ${id}` }; } } // Async functions always return a Promise console.log(fetchData()); // Promise {<fulfilled>: 'Data fetched'} // Equivalent to: function fetchDataPromise() { return Promise.resolve('Data fetched'); }
Await Keyword
javascript// Helper function that returns a Promise function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function fetchUser(id) { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id, name: `User ${id}`, email: `user${id}@example.com` }); } else { reject('Invalid user ID'); } }, 1000); }); } // Using await async function getUserData() { console.log('Fetching user...'); // Wait for the promise to resolve const user = await fetchUser(1); console.log('User received:', user); // Can use the result immediately console.log(`Welcome, ${user.name}!`); return user; } // Call async function getUserData().then(user => { console.log('Final result:', user); }); // Await can only be used inside async functions // This would cause a syntax error: // const user = await fetchUser(1); // SyntaxError
Error Handling with Try/Catch
Basic Error Handling
javascriptasync function handleErrors() { try { const user = await fetchUser(-1); // This will reject console.log('User:', user); } catch (error) { console.error('Error caught:', error); } } handleErrors(); // Output: Error caught: Invalid user ID // Multiple operations with error handling async function multipleOperations() { try { const user1 = await fetchUser(1); console.log('First user:', user1.name); const user2 = await fetchUser(2); console.log('Second user:', user2.name); const user3 = await fetchUser(-1); // This will fail console.log('Third user:', user3.name); // Won't execute } catch (error) { console.error('Operation failed:', error); } } multipleOperations();
Finally Block
javascriptasync function withFinally() { let resource = null; try { console.log('Acquiring resource...'); resource = await acquireResource(); console.log('Using resource...'); const result = await useResource(resource); return result; } catch (error) { console.error('Error using resource:', error); throw error; // Re-throw if needed } finally { // Cleanup always happens if (resource) { console.log('Releasing resource...'); await releaseResource(resource); } } } // Helper functions for example async function acquireResource() { await delay(100); return { id: 'resource-1', status: 'acquired' }; } async function useResource(resource) { await delay(200); return `Used ${resource.id}`; } async function releaseResource(resource) { await delay(50); console.log(`Released ${resource.id}`); }
Parallel vs Sequential Execution
Sequential Execution
javascript// Operations run one after another async function sequentialExecution() { console.time('Sequential'); const user1 = await fetchUser(1); // Wait 1 second const user2 = await fetchUser(2); // Wait another 1 second const user3 = await fetchUser(3); // Wait another 1 second console.timeEnd('Sequential'); // ~3 seconds total return [user1, user2, user3]; } sequentialExecution().then(users => { console.log('Sequential users:', users.length); });
Parallel Execution
javascript// Operations run simultaneously async function parallelExecution() { console.time('Parallel'); // Start all operations at once const promise1 = fetchUser(1); const promise2 = fetchUser(2); const promise3 = fetchUser(3); // Wait for all to complete const users = await Promise.all([promise1, promise2, promise3]); console.timeEnd('Parallel'); // ~1 second total return users; } parallelExecution().then(users => { console.log('Parallel users:', users.length); }); // Alternative syntax async function parallelAlternative() { console.time('Parallel Alternative'); const users = await Promise.all([ fetchUser(1), fetchUser(2), fetchUser(3) ]); console.timeEnd('Parallel Alternative'); return users; }
Mixed Sequential and Parallel
javascriptasync function mixedExecution() { try { // First, get user profile (sequential) const user = await fetchUser(1); console.log('Got user:', user.name); // Then fetch related data in parallel const [posts, friends, settings] = await Promise.all([ fetchUserPosts(user.id), fetchUserFriends(user.id), fetchUserSettings(user.id) ]); // Finally, process results sequentially const processedPosts = await processPosts(posts); const processedFriends = await processFriends(friends); return { user, posts: processedPosts, friends: processedFriends, settings }; } catch (error) { console.error('Mixed execution failed:', error); throw error; } } // Helper functions for example async function fetchUserPosts(userId) { await delay(500); return [`Post 1 by user ${userId}`, `Post 2 by user ${userId}`]; } async function fetchUserFriends(userId) { await delay(300); return [`Friend 1 of user ${userId}`, `Friend 2 of user ${userId}`]; } async function fetchUserSettings(userId) { await delay(200); return { theme: 'dark', notifications: true }; } async function processPosts(posts) { await delay(100); return posts.map(post => post.toUpperCase()); } async function processFriends(friends) { await delay(150); return friends.map(friend => `Processed: ${friend}`); }
Advanced Patterns
Async Iteration
javascript// Process array items sequentially async function processSequentially(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; } // Process array items in parallel with concurrency limit async function processWithConcurrency(items, concurrency = 3) { const results = []; for (let i = 0; i < items.length; i += concurrency) { const batch = items.slice(i, i + concurrency); const batchResults = await Promise.all( batch.map(item => processItem(item)) ); results.push(...batchResults); } return results; } async function processItem(item) { await delay(Math.random() * 1000); return `Processed: ${item}`; } // Usage const items = ['A', 'B', 'C', 'D', 'E', 'F']; processSequentially(items).then(results => { console.log('Sequential results:', results); }); processWithConcurrency(items, 2).then(results => { console.log('Concurrent results:', results); });
Async Generators
javascript// Async generator function async function* fetchPages(baseUrl, totalPages) { for (let page = 1; page <= totalPages; page++) { console.log(`Fetching page ${page}...`); // Simulate API call await delay(500); const data = { page, data: `Data for page ${page}`, hasMore: page < totalPages }; yield data; } } // Using async generator async function consumePages() { const pageGenerator = fetchPages('https://api.example.com', 3); for await (const page of pageGenerator) { console.log('Received page:', page); // Process page data if (page.data.includes('2')) { console.log('Found special page!'); } } console.log('All pages processed'); } // consumePages(); // Manual iteration async function manualIteration() { const generator = fetchPages('https://api.example.com', 2); let result = await generator.next(); while (!result.done) { console.log('Manual iteration:', result.value); result = await generator.next(); } }
Practical Examples
API Client with Async/Await
javascriptclass ApiClient { constructor(baseUrl) { this.baseUrl = baseUrl; this.defaultHeaders = { 'Content-Type': 'application/json' }; } async request(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const config = { headers: { ...this.defaultHeaders, ...options.headers }, ...options }; try { const response = await fetch(url, config); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } return await response.text(); } catch (error) { console.error(`API request failed: ${error.message}`); throw error; } } async get(endpoint, params = {}) { const queryString = new URLSearchParams(params).toString(); const url = queryString ? `${endpoint}?${queryString}` : endpoint; return this.request(url); } async post(endpoint, data) { return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }); } async put(endpoint, data) { return this.request(endpoint, { method: 'PUT', body: JSON.stringify(data) }); } async delete(endpoint) { return this.request(endpoint, { method: 'DELETE' }); } } // Usage const api = new ApiClient('https://jsonplaceholder.typicode.com'); async function demonstrateApiClient() { try { // GET request const posts = await api.get('/posts', { userId: 1 }); console.log('Posts:', posts.slice(0, 2)); // POST request const newPost = await api.post('/posts', { title: 'New Post', body: 'This is a new post', userId: 1 }); console.log('Created post:', newPost); } catch (error) { console.error('API demo failed:', error); } } // demonstrateApiClient();
File Processing with Async/Await
javascript// Simulated file operations async function readFile(filename) { await delay(100); if (filename.endsWith('.txt')) { return `Content of ${filename}`; } throw new Error(`Cannot read ${filename}`); } async function writeFile(filename, content) { await delay(150); console.log(`Wrote to ${filename}: ${content.substring(0, 20)}...`); } async function processFile(filename) { await delay(200); return `Processed ${filename}`; } // File processing pipeline async function processFiles(filenames) { const results = []; for (const filename of filenames) { try { console.log(`Processing ${filename}...`); // Read file const content = await readFile(filename); // Process content const processed = await processFile(filename); // Write result await writeFile(`processed_${filename}`, processed); results.push({ filename, status: 'success', processed }); } catch (error) { console.error(`Failed to process ${filename}:, error.message`); results.push({ filename, status: 'error', error: error.message }); } } return results; } // Usage const files = ['document.txt', 'image.jpg', 'data.txt']; processFiles(files).then(results => { console.log('Processing complete:', results); });
Retry Logic with Async/Await
javascriptasync function withRetry(operation, maxRetries = 3, delay = 1000) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`Attempt ${attempt}/${maxRetries}`); const result = await operation(); console.log('Operation succeeded'); return result; } catch (error) { lastError = error; console.error(`Attempt ${attempt} failed:, error.message`); if (attempt < maxRetries) { console.log(`Waiting ${delay}ms before retry...`); await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; // Exponential backoff } } } throw new Error(`Operation failed after ${maxRetries} attempts: ${lastError.message}`); } // Unreliable operation for testing async function unreliableOperation() { await delay(500); if (Math.random() < 0.7) { // 70% chance of failure throw new Error('Random failure'); } return 'Success!'; } // Usage async function testRetry() { try { const result = await withRetry(unreliableOperation, 5, 500); console.log('Final result:', result); } catch (error) { console.error('All retries failed:', error.message); } } // testRetry();
Async Queue Implementation
javascriptclass AsyncQueue { constructor(concurrency = 1) { this.concurrency = concurrency; this.running = 0; this.queue = []; } async add(task) { return new Promise((resolve, reject) => { this.queue.push({ task, resolve, reject }); this.process(); }); } async process() { if (this.running >= this.concurrency || this.queue.length === 0) { return; } this.running++; const { task, resolve, reject } = this.queue.shift(); try { const result = await task(); resolve(result); } catch (error) { reject(error); } finally { this.running--; this.process(); // Process next task } } get size() { return this.queue.length; } get pending() { return this.running; } } // Usage const queue = new AsyncQueue(2); // Max 2 concurrent tasks async function demonstrateQueue() { const tasks = Array.from({ length: 10 }, (_, i) => () => delay(1000).then(() => `Task ${i + 1} completed`) ); console.log('Adding tasks to queue...'); const promises = tasks.map(task => queue.add(task)); // Monitor queue progress const monitor = setInterval(() => { console.log(`Queue size: ${queue.size}, Running: ${queue.pending}`); }, 500); try { const results = await Promise.all(promises); console.log('All tasks completed:', results); } finally { clearInterval(monitor); } } // demonstrateQueue();