Async Code in Node.js: Callbacks and Promises
1. Why async code exists in Node.js
Node.js runs on a single-threaded event loop
Blocking operations (like file I/O, network calls) would freeze the app
Async lets Node:
Handle multiple tasks efficiently
Stay responsive under load
2. Start with a simple file reading scenario
Callback-based approach
const fs = require('fs');
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file');
return;
}
console.log('File content:', data);
});
3. Callback flow (step-by-step)
readFileis calledNode registers the callback
File read happens in background (non-blocking)
Once done → callback pushed to event loop queue
Event loop executes callback
4. Problems with nested callbacks (Callback Hell)
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) return;
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) return;
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) return;
console.log(data1, data2, data3);
});
});
});
Issues:
Hard to read 😵
Difficult error handling
Poor scalability
Pyramid-shaped code
5. Promise-based async handling
const fs = require('fs').promises;
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log(data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log(data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log(data3);
})
.catch(err => {
console.error('Error:', err);
});
6. Benefits of Promises
Cleaner chaining (no nesting)
Centralized error handling (
.catch)Better readability
Easier to extend and debug
7. Callback vs Promise (Quick Comparison)
| Aspect | Callback | Promise |
|---|---|---|
| Structure | Nested | Chain-based |
| Readability | Poor (deep nesting) | Cleaner |
| Error handling | Multiple checks | Single .catch() |
| Scalability | Difficult | Easier |
8. Flow
Callback Execution Chain
readFile → callback → readFile → callback → readFile → callback
(nested inside each other)
Promise Lifecycle Flow
Pending → Fulfilled (.then)
→ Rejected (.catch)
Final Suggestion
Avoid deep callbacks
Prefer Promises (or even better:
async/await)Keep async code flat and readable




