What is a Callback?
A callback is simply a function passed as an argument to another function so that it can be executed later.
Example:
function greet(name) {
console.log("Hello " + name);
}
function processUser(callback) {
callback("Saravanan");
}
processUser(greet);
Output
Hello Saravanan
Here, greet is the callback function.
Why do we need callbacks?
Some tasks take time to complete.
For example:
- Downloading data from a server
- Reading a file
- Waiting for a timer
- Fetching API data
JavaScript doesn't stop the entire program while waiting.
Instead, it says:
"Continue doing other work. When this task finishes, I'll call the callback."
Example:
console.log("Start");
setTimeout(() => {
console.log("Task Completed");
}, 2000);
console.log("End");
Output
Start
End
Task Completed
Notice that JavaScript didn't wait for 2 seconds before printing End.
The Problem: Callback Hell
Imagine we're building software using the SDLC process.
The order should be:
Planning
↓
Design
↓
Development
↓
Testing
↓
Deployment
Using callbacks:
function planning(callback) {
setTimeout(() => {
console.log("Planning Completed");
callback();
}, 1000);
}
function design(callback) {
setTimeout(() => {
console.log("Design Completed");
callback();
}, 1000);
}
function development(callback) {
setTimeout(() => {
console.log("Development Completed");
callback();
}, 1000);
}
function testing(callback) {
setTimeout(() => {
console.log("Testing Completed");
callback();
}, 1000);
}
function deploy() {
console.log("Deployment Completed");
}
planning(() => {
design(() => {
development(() => {
testing(() => {
deploy();
});
});
});
});
Although the program works, there's a problem.
The code keeps moving further to the right.
planning(
design(
development(
testing(
deploy()
)
)
)
)
This deeply nested structure is called Callback Hell.
Problems with Callback Hell:
- Difficult to read
- Difficult to debug
- Difficult to maintain
- Becomes messy as the project grows
Solution: Promise
A Promise represents the result of an asynchronous operation.
It can be in one of three states:
- Pending
- Fulfilled (Resolved)
- Rejected
Creating a Promise:
const planning = () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Planning Completed");
resolve();
}, 1000);
});
};
The Promise either calls:
resolve();
when successful,
or
reject();
when something goes wrong.
Promise Chaining
Now we can execute tasks one after another without nesting.
planning()
.then(() => design())
.then(() => development())
.then(() => testing())
.then(() => deploy())
.catch(() => console.log("Something went wrong."));
This code is much easier to read.
It clearly says:
Planning
→ Design
→ Development
→ Testing
→ Deploy
Even Cleaner: async/await
JavaScript introduced async and await to make Promise-based code look more like normal synchronous code.
async function SDLC() {
try {
await planning();
await design();
await development();
await testing();
await deploy();
} catch (error) {
console.log(error);
}
}
SDLC();
This is usually easier to understand because the steps appear in the same order they execute.
Callback Hell vs Promise
| Callback Hell | Promise |
|---|---|
| Deeply nested code | Flat and readable code |
| Hard to maintain | Easy to maintain |
| Difficult error handling | Centralized error handling using .catch()
|
| Less readable as the project grows | Easier to understand and extend |
References:
https://www.w3schools.com/js/js_promise.asp
https://www.geeksforgeeks.org/javascript/javascript-promise/
Top comments (1)
Mr saravanan
What is a callback function?
What is the Event Loop in JavaScript?