17. Asynchronous Javascript - Part 2 : CallBacks , Callback hell , Promises , async/await
Callbacks
A callback function is a function passed as an argument to another function and executed later.
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched");
callback();
}, 2000);
}
function processData() {
console.log("Processing data...");
}
fetchData(processData);
Read More about callbacks in previous article:
Issues with Callbacks
Hard to manage in complex applications.
Leads to callback hell when multiple nested callbacks exist.
Callback hell
Callback Hell (also known as Pyramid of Doom) occurs when multiple asynchronous operations are nested inside each other, making the code unreadable and difficult to maintain.
function step1(callback) { setTimeout(() => { console.log("Step 1 completed"); callback(); }, 1000); } function step2(callback) { setTimeout(() => { console.log("Step 2 completed"); callback(); }, 1000); } function step3(callback) { setTimeout(() => { console.log("Step 3 completed"); callback(); }, 1000); } step1(() => { step2(() => { step3(() => { console.log("All steps completed"); }); }); }); /* Step 1 completed Step 2 completed Step 3 completed All steps completed
Problems with Callback Hell
Code becomes deeply nested.
Hard to debug and maintain.
Difficult to handle errors.
To solve these issues, Promises were introduced.
Promises
In JS Promises is a good way to handle asynchronous operations . It is used to find out if the asynchronous operation is successfully completed or not.
It has three states:
Pending: Initial state.
Resolved (Fulfilled): Operation completed successfully.
Rejected: Operation failed.
//Step 1: Creating a promise const ticket = new Promise((resolve, reject)=>{ const isBoarded = false; // our promise is reject if(isBoarded){ resolve("Your is on the time"); } else{ reject("Your flight has been cancelled"); } }) // Step 2:use a promise -> .then if resolve , .catch() if reject ticket.then((data)=>{ // data is that is written inside reolve () or reject() console.log("WOW", data) }).catch((data)=>{ console.log("Oh No",data) // Output: Oh No Your flight has been cancelled })
Promise solve the problem of callback hell : Chaining Promises
function step1() { return new Promise((reolve,reject)=>{ setTimeout(() => { console.log("Step 1 completed"); reolve() }, 1000); }) } function step2() { return new Promise((reolve,reject)=>{ setTimeout(() => { console.log("Step 2 completed"); reolve() }, 1000); }) } function step3() { return new Promise((reolve,reject)=>{ setTimeout(() => { console.log("Step 3 completed"); reolve() }, 1000); }) } step1().then(() => step2()) // return promise .then(() => step3()) .then(() => console.log("All steps completed")) .catch((error) => console.error("Error:", error)); /* Step 1 completed Step 2 completed Step 3 completed All steps completed
Async/Await
async/await
is a cleaner way to handle promises. It makes asynchronous code look like synchronous code.// Step 1: Creating a promise const ticket = new Promise((resolve, reject) => { const isBoarded = false; // our promise is rejected if (isBoarded) { resolve("Your flight is on time"); } else { reject("Your flight has been cancelled"); } }); // Step 2: Using async/await async function checkFlight() { try { const data = await ticket; // Waiting for the promise to resolve console.log("WOW", data); } catch (error) { console.log("Oh No", error); // Output: Oh No Your flight has been cancelled } } // Calling the function checkFlight();
Error Handling in Async/Await:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = false;
if (success) {
resolve("Data fetched successfully");
} else {
reject("Error fetching data");
}
}, 2000);
});
}
async function getData() {
try {
let data = await fetchData();
console.log(data);
} catch (error) {
console.log(error);
}
}
getData();
Advantages of Async/Await:
Improves readability.
Easier error handling with
try/catch
.Avoids chaining problems in promises.
Interview Questions
How do Promises solve callback hell?
Promises allow chaining
.then()
instead of deeply nesting callbacks.
Predict the output ?
console.log("Start"); setTimeout(() => console.log("Timeout"), 0); Promise.resolve().then(() => console.log("Promise")); console.log("End"); /* Start End Promise Timeout
Synchronous logs
"Start"
and"End"
.Promise.then() executes before
setTimeout()
due to microtask queue priority.
Predict the output ?
async function foo() { console.log(1); await console.log(2); console.log(3); } console.log(4); foo(); console.log(5); /* 4 1 2 5 3
Explanation:
await console.log(2)
logs2
, butawait
makesconsole.log(3)
execute later.console.log(5)
executes beforeconsole.log(3)
.