Mastering Asynchronous Operations in Node.js: Practical Strategies
Asynchronous programming is at the heart of Node.js. Here are practical strategies to master async operations. Understanding Async Patterns Callbacks // Traditional callback pattern function readFile(callback) { fs.readFile('file.txt', 'utf8', (err, data) => { if (err) return callback(err); callback(null, data); }); } Promises // Promise-based function readFile() { return new Promise((resolve, reject) => { fs.readFile('file.txt', 'utf8', (err, data) => { if (err) reject(err); else resolve(data); }); }); } Async/Await // Modern async/await async function readFile() { try { const data = await fs.promises.readFile('file.txt', 'utf8'); return data; } catch (err) { throw err; } } Common Patterns Sequential Execution // Execute operations one after another async function sequential() { const user = await getUser(userId); const profile = await getProfile(user.id); const posts = await getPosts(user.id); return { user, profile, posts }; } Parallel Execution // Execute operations in parallel async function parallel() { const [user, profile, posts] = await Promise.all([ getUser(userId), getProfile(userId), getPosts(userId) ]); return { user, profile, posts }; } Race Conditions // Get first resolved promise async function race() { const result = await Promise.race([ fetchFromAPI1(), fetchFromAPI2(), timeout(5000) // Fallback ]); return result; } Error Handling Try-Catch with Async/Await async function handleErrors() { try { const data = await fetchData(); return data; } catch (error) { if (error.code === 'ENOENT') { // Handle file not found } else if (error.code === 'ECONNREFUSED') { // Handle connection refused } else { // Handle other errors } throw error; } } Promise Error Handling // Chain error handling fetchData() .then(data => processData(data)) .catch(error => { console.error('Error:', error); return fallbackData(); }) .finally(() => { cleanup(); }); Advanced Patterns Retry Logic async function retry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; await sleep(1000 * (i + 1)); // Exponential backoff } } } Timeout Pattern function withTimeout(promise, ms) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms) ) ]); } // Usage const data = await withTimeout(fetchData(), 5000); Batch Processing async function processBatch(items, batchSize = 10) { for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); await Promise.all(batch.map(item => processItem(item))); } } Best Practices 1. Always Await Promises // Bad: Fire and forget asyncFunction(); // Unhandled promise // Good: Always await await asyncFunction(); 2. Use Promise.all for Independent Operations // Good: Parallel execution const [users, posts, comments] = await Promise.all([ getUsers(), getPosts(), getComments() ]); 3. Handle Rejections // Always handle promise rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason); }); Conclusion Mastering async operations requires: ...
