JavaScript Async
1️ What is a Callback?
A callback is a function passed as an argument to another function. It runs after some task is completed.
We use callbacks mainly for:
API calls
Reading files
Timers
Database operations
Because JavaScript is asynchronous, some operations take time. Instead of waiting (blocking), JavaScript continues running other code and calls the callback later.
Real Life Example
You order food.
You give your phone number.
When food is ready, they call you.
That phone number function = callback
Example
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched");
callback(); // callback function call
}, 2000);
}
function processData() {
console.log("Processing data....");
}
fetchData(processData);
Output
Data fetched
Processing data....
Problem with Callbacks
Callback Hell (Pyramid of Doom)
When we have multiple async operations, callbacks become nested and messy.
Example
setTimeout(() => {
console.log("Step 1");
setTimeout(() => {
console.log("Step 2");
setTimeout(() => {
console.log("Step 3");
}, 1000);
}, 1000);
}, 1000);
Problems:
Hard to read
Inversion of Control
Hard to maintain
Hard to debug
Error handling is hard
What is a Promise?
A Promise is an object that represents the future value of the async operation or task:
“I promise I will give you a result later.”
It has 3 states:
Pending – Waiting
Resolved (Fulfilled) – Success
Rejected – Failed
Promise Object is Inmutable.
Promises Fix the Callback Problems:
No deep nesting
Better error handling
Cleaner code
Can chain using
.then()No inversion of control. Here, we attach the functions; weare not passing the function.
Basic Promise Example
const myPromise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Task completed!");
} else {
reject("Task failed!");
}
});
myPromise
.then(result => {
console.log(result); // success
})
.catch(error => {
console.log(error); // error
});
Sequential Order Using Promises
Tasks run one after another.
function step1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("Step 1 done");
resolve();
}, 1000);
});
}
function step2() {
return new Promise(resolve => {
setTimeout(() => {
console.log("Step 2 done");
resolve();
}, 1000);
});
}
step1()
.then(step2)
.then(() => {
console.log("All steps completed");
});
Parallel Order Using Promises
All tasks run at the same time.
Using Promise.all()
const p1 = new Promise(resolve =>
setTimeout(() => resolve("Task 1"), 2000)
);
const p2 = new Promise(resolve =>
setTimeout(() => resolve("Task 2"), 1000)
);
Promise.all([p1, p2])
.then(results => {
console.log(results);
// ["Task 1", "Task 2"]
});
Promise APIs
1. Promise.all()
Waits for all promises
Fails if one fails
Fail Fast
2. Promise.race()
- Returns first finished promise < fail / success>
Promise.race([p1, p2])
.then(result => {
console.log(result); // Fastest one
});
3 Promise.allSettled()
Waits for all
Returns success + failure both -> {status ,reuslt}
Promise.allSettled([p1, p2])
.then(results => {
console.log(results);
});
4 Promise.any()
- Returns the first successful promise
Problems with Promises
Even though promises are better than callbacks, they still have issues:
1. Long .then() chains
Still hard to read.
2. Error handling confusion
Sometimes .catch() may not catch nested errors.
3. Mixing sync & async logic gets confusing
What is async/await?
async/await It is built on top of promises.
It makes async code look like normal synchronous code.
Example with async/await
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function run() {
console.log("Start");
await delay(2000); // wait here
console.log("After 2 seconds");
}
run();
How async/await Solves Promise Problems
No .then() chaining
Looks clean
Easy error handling with try/catch
More readable
Error Handling Example
function fetchData() {
return new Promise((resolve, reject) => {
reject("Something went wrong");
});
}
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.log("Error:", error);
}
}
getData();
Sequential with async/await
async function runSteps() {
await step1();
await step2();
console.log("All done");
}
runSteps();
Parallel with async/await
async function runParallel() {
const results = await Promise.all([p1, p2]);
console.log(results);
}
runParallel();
We prefer: async/await
Feature | Callback | Promise | Async/Await |
|---|---|---|---|
Readability | ❌ Hard | 😐 Medium | ✅ Easy |
Error Handling | ❌ Messy | 😐 Better | ✅ Clean |
Debugging | ❌ Hard | 😐 Medium | ✅ Easy |
Modern Usage | ❌ Old | ✅ Yes | ✅ Best |
What is the Event Loop?
JavaScript is single-threaded (one call stack).
The Event Loop allows JavaScript to handle asynchronous tasks (like setTimeout, fetch, Promise) without blocking the main thread.
It decides when async callbacks should run.
Main Components
1. Call Stack
Executes synchronous code (LIFO).
2. Web APIs (Browser/Node APIs)
Handle async operations like:
setTimeoutfetchDOM events
console
LocalStorage
3. Microtask Queue
Contains:
Promise.thencatchfinallyqueueMicrotaskDom Mutation
4. Macrotask Queue (Callback Queue)
Contains:
setTimeoutsetInterval
5. Event Loop
Continuously checks:
If Call Stack is empty →
Run ALL Microtasks →
Run ONE Macrotask →
Repeat
Execution Order Rule
Synchronous Code
→ Microtasks
→ One Macrotask
→ Repeat
Example – setTimeout
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
Output
Start
End
Timeout
Even 0ms timeout waits until the stack is empty.
Example – Promise vs setTimeout
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output
Start
End
Promise
Timeout
Why?
Promise → Microtask (higher priority)
setTimeout → Macrotask (lower priority)
Microtasks always run before Macrotasks.
Event Loop executes synchronous code first, then all microtasks, then one macrotask — and repeats.
Async/await and Event Loop
async function test() {
console.log("1");
await Promise.resolve();
console.log("2");
}
console.log("3");
test();
console.log("4");
Output
3
1
4
2
await pauses function and moves the remaining code to the Microtask Queue.
Conclusion
JavaScript runs one thing at a time.
Async tasks don’t run immediately.
Microtasks run before macrotasks.
Blocking code stops the event loop.
Promise callbacks have a higher priority than
setTimeout.Callbacks were first → caused callback hell
Promises improved structure
Async/Await made it clean and readable
Today → Use async/await with Promise APIs