JavaScript is
single-threaded, meaning it executes
one task at a time.
But modern web applications must handle:
- API requests
- Timers
- Database operations
- File reading
- User events
- Animations
If JavaScript waited for every slow task to finish, the entire webpage would freeze like a locked gate in a storm.
To solve this, JavaScript uses Asynchronous Programming.
1. What is Synchronous JavaScript?
In synchronous code, tasks run line by line.
console.log("Start");
console.log("Middle");
console.log("End");
Output
Start
Middle
End
Each statement waits for the previous one to complete.
2. Problem with Synchronous Code
Imagine downloading data from a server.
console.log("Fetching data...");
// Assume this takes 5 seconds
heavyTask();
console.log("Done");
During those 5 seconds:
- UI freezes
- Buttons stop working
- Website becomes unresponsive
This is bad user experience.
3. What is Asynchronous JavaScript?
Asynchronous JavaScript allows:
“Start a task now, finish it later, and continue executing other code meanwhile.”
Example:
console.log("Start");
setTimeout(() => {
console.log("Task Finished");
}, 3000);
console.log("End");
Output
Start
End
Task Finished
The timer runs in background while JavaScript continues.
4. How JavaScript Handles Async Operations
JavaScript uses:
- Call Stack
- Web APIs
- Callback Queue
- Event Loop
These work together like an organized system.
5. The JavaScript Runtime Architecture
Step-by-Step Flow
Call Stack → Web APIs → Callback Queue → Event Loop
(A) Call Stack
The place where functions execute.
Example:
function hello() {
console.log("Hello");
}
hello();
hello() enters stack → executes → removed.
(B) Web APIs
Provided by browser or Node.js.
Examples:
- setTimeout
- fetch
- DOM events
- geolocation
These are NOT part of JavaScript itself.
(C) Callback Queue
Completed async tasks wait here.
(D) Event Loop
Checks:
“Is call stack empty?”
If YES → moves callback from queue to stack.
6. Understanding setTimeout()
Syntax
setTimeout(function, delay);
Example:
setTimeout(() => {
console.log("Hello after 2 seconds");
}, 2000);
Important Logic
Even if delay is 0, it still waits until stack becomes empty.
Example:
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
Output
A
C
B
Because async callbacks always wait for stack clearance.
7. Callbacks in JavaScript
A callback is:
A function passed as an argument to another function.
Example:
function greet(name, callback) {
console.log("Hello " + name);
callback();
}
function bye() {
console.log("Goodbye");
}
greet("Anas", bye);
Output
Hello Anas
Goodbye
8. Asynchronous Callback Example
setTimeout(() => {
console.log("Data Loaded");
}, 2000);
The function executes later.
9. Callback Hell
When many async operations depend on each other.
Example:
setTimeout(() => {
console.log("Step 1");
setTimeout(() => {
console.log("Step 2");
setTimeout(() => {
console.log("Step 3");
}, 1000);
}, 1000);
}, 1000);
This pyramid structure becomes difficult to manage.
Problems:
- Hard to read
- Hard to debug
- Hard to maintain
This is called:
“Callback Hell” or “Pyramid of Doom”
10. Promises in JavaScript
Promises were introduced to solve callback hell.
A Promise represents:
Future Success OR Failure
11. Promise States
A Promise has 3 states:
| State |
Meaning |
| Pending |
Still running |
| Fulfilled |
Success |
| Rejected |
Failed |
12. Creating a Promise
const promise = new Promise((resolve, reject) => {
let success = true;
if(success) {
resolve("Task completed");
} else {
reject("Task failed");
}
});
13. Consuming Promises
Using .then() and .catch()
promise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
14. Real Example of Promise
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received from server");
}, 2000);
});
}
fetchData()
.then((data) => {
console.log(data);
});
Output after 2 seconds
Data received from server
15. Promise Chaining
fetchData()
.then((data) => {
console.log(data);
return "Next Step";
})
.then((msg) => {
console.log(msg);
});
16. Handling Errors
fetchData()
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
17. Async and Await
async/await makes asynchronous code look synchronous and cleaner.
Introduced in ES8.
18. Async Function
async function hello() {
return "Hello";
}
Async functions automatically return promises.
19. Await Keyword
await pauses execution until promise resolves.
Example:
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Server Data");
}, 2000);
});
}
async function getData() {
const data = await fetchData();
console.log(data);
}
getData();
Output after 2 seconds
Server Data
20. Error Handling with Try-Catch
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch(error) {
console.log(error);
}
}
21. Fetch API
Used to request data from servers/APIs.
Basic Fetch Example
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
});
22. Fetch Using Async/Await
async function getUsers() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const data = await response.json();
console.log(data);
}
getUsers();
23. Why Async/Await is Better
| Callback |
Promise |
Async/Await |
| Complex |
Better |
Cleanest |
| Nested |
Flat |
Simple |
| Hard debugging |
Easier |
Very easy |
| Less readable |
Good |
Excellent |
24. Parallel Async Operations
Promise.all()
Runs multiple promises together.
const p1 = Promise.resolve("A");
const p2 = Promise.resolve("B");
const p3 = Promise.resolve("C");
Promise.all([p1, p2, p3])
.then((result) => {
console.log(result);
});
Output
["A", "B", "C"]
25. Promise.race()
Returns first completed promise.
Promise.race([p1, p2, p3])
.then((result) => {
console.log(result);
});
26. Microtasks vs Macrotasks
Very important interview topic.
Macrotasks
Examples:
Microtasks
Examples:
Microtasks execute before macrotasks.
Example
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output
Start
End
Promise
Timeout
Because:
Microtask Queue > Macrotask Queue
Priority is given to microtasks.
27. Event Loop Visualization
1. Execute Call Stack
2. Check Microtask Queue
3. Execute all Microtasks
4. Check Macrotask Queue
5. Repeat forever
This endless cycle powers asynchronous JavaScript.
28. Real-World Use Cases
| Feature |
Async Used? |
| API Calls |
Yes |
| Chat Apps |
Yes |
| Live Notifications |
Yes |
| Video Streaming |
Yes |
| File Uploads |
Yes |
| Database Queries |
Yes |
Without async programming, modern web apps cannot exist.
29. Common Interview Questions
Q1: Is JavaScript synchronous or asynchronous?
JavaScript is:
Single-threaded and synchronous by nature
But it can handle async operations using:
- Event Loop
- Web APIs
- Callbacks
- Promises
Q2: Difference between callback and promise?
| Callback |
Promise |
| Function-based |
Object-based |
| Causes nesting |
Cleaner |
| Hard error handling |
Better error handling |
Q3: Difference between async/await and promise?
async/await is syntactic sugar over promises.
Promises still work underneath.
30. Complete Real-World Example
function loginUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("User Logged In");
}, 2000);
});
}
function getProfile() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Profile Data");
}, 1000);
});
}
async function app() {
console.log("Loading...");
const user = await loginUser();
console.log(user);
const profile = await getProfile();
console.log(profile);
console.log("Application Ready");
}
app();
Output Flow
Loading...
(after 2 sec)
User Logged In
(after 1 sec)
Profile Data
Application Ready
Final Core Understanding
Asynchronous JavaScript is built on:
1. Call Stack
2. Web APIs
3. Callback Queue
4. Event Loop
5. Promises
6. Async/Await
Master these deeply, and JavaScript begins to feel less like chaos and more like rhythm — like caravans moving through time without blocking each other’s path.