Async Code in Node.js: Callbacks and Promises

Why async code exists in Node.js
First, we have to understand why we need async behaviour in JavaScript
As we all know, JavaScript is a single-threaded language that runs or executes line by line.
Let's suppose you want to read or download your data. So what do you prefer? Do you want to wait for the data or do something else because it takes time, right?
Now, what should your app do while waiting? Just sit idle, or give you access to other features
Node.js is single-threaded
It cannot do many things at once, like Java or Python threads
So it uses asynchronous (non-blocking) behavior
This is why async code exists in NodeJs
Example:
const fs = require("fs");
fs.readFile("file.txt", "utf8", (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data);
});
console.log("File reading started...");
readFilestarts readingNode DOES NOT wait
"File reading started..."prints firstWhen file is ready → callback runs
This clearly shows async behavior
Callback-based async execution
A callback is just a function passed inside another function
It runs later when the task is completed
Example:
function fetchData(callback) {
setTimeout(() => {
callback("Data received");
}, 2000);
}
Problems with nested callbacks
Nested callbacks are okay, but when it goes deeper and deeper, It becomes very complex to understand and debug. which is known as callback hell. It looks like a pyramid structure
Example:
getUser(userId, (user) => {
getPosts(user, (posts) => {
getComments(posts, (comments) => {
console.log(comments);
});
});
});
Hard to read and understand
Hard to debug
Too many nested functions
Promise-based async handling
A Promise represents a value that may be available now, later, or never.
A promise has three states:
Pending
Fulfilled
Rejected
Example:
const fs = require("fs").promises;
fs.readFile("data.txt", "utf8")
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
Here:
.then()handles success.catch()handles errors
Promise looks cleaner and easier to understand
Benefits of promises
Better Readability: Code looks cleaner and more organized.
Better Error Handling: A single .catch() can handle errors from the entire chain.
Avoids Callback Hell: No deep nesting of functions.
Easier Maintenance: Future developers can understand the code more easily.
Conclusion
Callbacks were the original way to handle asynchronous operations in Node.js. They work well for simple tasks, but multiple nested callbacks can make code difficult to read and maintain.
Promises provide a cleaner and more structured way to manage asynchronous operations. They improve readability, simplify error handling, and help avoid callback hell.
Because of these advantages, promises are commonly used in modern Node.js applications.




