Node.js excels at asynchronous programming because it's built on a non-blocking I/O model. This means while one operation is waiting (e.g., reading a file or making a network request), the event loop can handle other tasks. Understanding async patterns is essential for writing efficient Node.js code.
Callbacks are the original async pattern in Node.js. A callback is a function passed as an argument to another function that gets called when the operation completes.
const fs = require('fs');
// Asynchronous read with callback
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content:', data);
});
console.log('Reading file...'); // This runs BEFORE the callback
Nesting callbacks leads to deeply indented code that's hard to read and maintain:
fs.readdir('folder', (err, files) => {
if (err) throw err;
fs.readFile(files[0], 'utf8', (err, data) => {
if (err) throw err;
fs.writeFile('output.txt', data, (err) => {
if (err) throw err;
console.log('Done!');
});
});
});
Promises provide a cleaner way to handle asynchronous operations. A Promise represents a value that may be available now, in the future, or never.
const fs = require('fs').promises;
// Asynchronous read with Promise
fs.readFile('data.txt', 'utf8')
.then(data => {
console.log('File content:', data);
return fs.writeFile('output.txt', data);
})
.then(() => console.log('Successfully wrote to output.txt'))
.catch(err => console.error('Error:', err));
console.log('Reading file...'); // This still runs first
function delay(ms) {
return new Promise((resolve, reject) => {
if (ms < 0) {
reject(new Error('Delay must be positive'));
} else {
setTimeout(() => resolve(`Delayed by ${ms}ms`), ms);
}
});
}
delay(1000)
.then(message => console.log(message))
.catch(err => console.error(err));
Async/await is syntactic sugar over promises, making asynchronous code read like synchronous code. It's the recommended approach in modern Node.js.
const fs = require('fs').promises;
async function processFiles() {
try {
const data = await fs.readFile('data.txt', 'utf8');
console.log('File content:', data);
await fs.writeFile('output.txt', data);
console.log('Successfully wrote to output.txt');
} catch (err) {
console.error('Error:', err);
}
}
processFiles();
async function readMultipleFiles() {
const files = ['file1.txt', 'file2.txt', 'file3.txt'];
const promises = files.map(f => fs.readFile(f, 'utf8'));
const results = await Promise.all(promises);
return results;
}
readMultipleFiles().then(console.log);
err parameter in the callback..catch() at the end of the chain.try/catch blocks.// Global unhandled promise rejection handler
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});