Topic 4 of 55
Async Patterns
Overview
Node.js is fundamentally asynchronous. Understanding callbacks, promises, async/await, and streams is critical for writing non-blocking server code. Poor async handling is the most common source of Node.js bugs and memory leaks.
Syntax
javascript
// Callbacks (old style — avoid)
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) return console.error(err);
console.log(data);
});
// Promisified (modern)
import { readFile, writeFile } from 'fs/promises';
async function processFile(path) {
const content = await readFile(path, 'utf8');
const processed = content.toUpperCase();
await writeFile('output.txt', processed);
return processed;
}
// Promise.all for parallel I/O
const [users, products, orders] = await Promise.all([
db.users.findAll(),
db.products.findAll(),
db.orders.findRecent(),
]);
// Streams (for large files)
import { createReadStream, createWriteStream } from 'fs';
createReadStream('huge.csv').pipe(processStream).pipe(createWriteStream('out.csv'));Common Pitfalls
- Unhandled promise rejections crash Node.js in newer versions (v15+). Always use try/catch in async functions.
- Reading an entire large file with fs.readFile loads it into RAM — use streams for files > a few hundred MB.
- Interview tip: process.nextTick() runs before any I/O events (even Promises). setImmediate() runs after I/O events. Both are next-tick utilities but have different queue priorities.
Real-World Example
Processing a large CSV file with streams (memory efficient):
example
javascript
import { createReadStream } from 'fs';
import { createInterface } from 'readline';
import { pipeline } from 'stream/promises';
import { Transform } from 'stream';
async function processLargeCSV(inputPath, outputPath) {
const fileStream = createReadStream(inputPath);
const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
let processedRows = 0;
const results = [];
for await (const line of rl) {
if (processedRows === 0) { processedRows++; continue; } // skip header
const [userId, amount, date, status] = line.split(',');
if (status.trim() === 'completed' && parseFloat(amount) > 1000) {
results.push({ userId, amount: parseFloat(amount), date });
}
processedRows++;
// Process in batches of 1000 to avoid memory issues
if (results.length >= 1000) {
await db.analytics.bulkInsert(results);
results.length = 0; // clear array
}
}
if (results.length > 0) await db.analytics.bulkInsert(results);
console.log(`Processed ${processedRows} rows`);
}