Callbacks, Promises & Async – await:
Callbacks → Callback Hell → Promises → Promise Chaining → Async/Await → IIFE
Synchronous vs Asynchronous
Synchronous
Code runs line by line. Each instruction waits for the previous one to finish before moving on. Can block the UI if a task is slow.
Asynchronous
Slow tasks (timers, API calls) are handed off. Remaining code keeps running. Result is handled later when ready.
// Async demo with setTimeout
console.log("one");
console.log("two");
setTimeout(() => {
console.log("hello"); // runs after 4s
}, 4000);
console.log("three");
console.log("four");
// Output: one → two → three → four → hello
Callbacks
A callback is a function passed as an argument to another function and called inside it. It's how JS handles "do this, then do that" logic.
function sum(a, b) {
console.log(a + b);
}
function calculator(a, b, sumCallback) {
sumCallback(a, b); // sum is called here
}
calculator(1, 2, sum); // Output: 3
Callback Hell (Pyramid of Doom)
When callbacks are nested inside each other to sequence async tasks, code becomes deeply indented and hard to read/maintain. This is called Callback Hell.
function getData(dataId, getNextData) {
setTimeout(() => {
console.log("data", dataId);
if (getNextData) getNextData();
}, 2000);
}
// Callback Hell ↓
getData(1, () => {
console.log("getting data2...");
getData(2, () => {
console.log("getting data3...");
getData(3, () => {
console.log("getting data4...");
getData(4);
});
});
});
The deeper the nesting, the harder to read, debug, and maintain. Promises solve this.
Promises A Promise is a JS object representing the eventual completion or failure of an async task. It takes a function with two handlers: resolve (success) and reject (failure).
Pending
Result is undefined. Task still in progress.
Fulfilled
resolve(value) was called. Task succeeded.
Rejected
reject(error) was called. Task failed.
// Creating a Promise
const getPromise = () => {
return new Promise((resolve, reject) => {
console.log("I am a promise");
resolve("success"); // OR
// reject("error");
});
};
// Consuming a Promise
let promise = getPromise();
promise.then((res) => {
console.log("Fulfilled:", res); // runs on resolve
});
promise.catch((err) => {
console.log("Rejected:", err); // runs on reject
});
resolve and reject are callbacks provided by JS — you just call them. If you reject without .catch(), you get an Uncaught (in promise) error in console.
Promise Chaining
Instead of nesting, return a new Promise inside .then() and chain another .then(). This keeps code flat and readable — the fix for callback hell.
function getData(dataId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("data", dataId);
resolve("success");
}, 3000);
});
}
// Promise Chain (clean & flat!)
getData(1)
.then((res) => { return getData(2); })
.then((res) => { return getData(3); })
.then((res) => { console.log(res); });
Async / Await
async before a function makes it always return a Promise.
await pauses execution inside the async function until a Promise settles — making async code look and read like synchronous code.
function getData(dataId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("data", dataId);
resolve("success");
}, 2000);
});
}
// Async-Await (cleanest way!)
async function getAllData() {
console.log("getting data1...");
await getData(1); // waits here
console.log("getting data2...");
await getData(2); // then waits here
console.log("getting data3...");
await getData(3);
}
getAllData();
IIFE — Immediately Invoked Function Expression
An IIFE is a function that runs immediately as soon as it is defined. Useful to run async code at the top level without wrapping it in a named function.
// Regular IIFE
(function () {
// runs immediately
})();
// Arrow IIFE
(() => {
// runs immediately
})();
// Async IIFE — most common use case
(async () => {
console.log("getting data1...");
await getData(1);
console.log("getting data2...");
await getData(2);
})();
Pattern: (func)() — wrap function in () to make it an expression, then call it with () immediately.
Summary
Callbacks → Callback Hell problem → Promises fix it → Promise Chaining improves readability → Async/Await makes it look synchronous → IIFE lets you run async code immediately