In my previous post, "JavaScript Async Explained Like a Grocery Store 🛒", I covered how the Call Stack, Browser APIs, Event Loop, and Queues work together to handle asynchronous operations.
In this post, we'll answer the next set of questions:
- Why do Promises exist?
- How does
fetch()return data if the API call takes time? - What exactly does
.then()do? - Why was
async/awaitintroduced? - Does
awaitblock JavaScript?
Let's dive in.
The Problem: Waiting Is Expensive
Imagine ordering a pizza online.
After placing the order, would you stand at the door doing absolutely nothing until the delivery arrives?
Probably not.
You would continue:
- Watching YouTube
- Working
- Talking to friends
- Doing other tasks
When the pizza arrives, you'll handle it.
JavaScript behaves the same way.
Operations like:
- API requests
- Database calls
- File operations
- Timers
can take seconds to complete.
If JavaScript waited for each operation to finish, your application would freeze.
Instead, JavaScript continues executing other code while waiting for the result.
Enter Promises
A Promise represents a value that will be available sometime in the future.
const promise = fetch("/users");
At this moment, the user data isn't available yet.
The Promise is basically saying:
"I don't have the result right now, but I'll let you know when it's ready."
A Promise can be in one of three states:
Pending
↓
Fulfilled (Success)
OR
Rejected (Failure)
Where is new Promise()?
One question confused me when I first learned Promises.
If Promises are important, why don't I see this everywhere?
new Promise((resolve, reject) => {
// async work
});
Instead, I often see:
fetch("/users")
.then(...)
The reason is simple:
Most modern APIs already return Promises for us.
For example:
fetch("/users");
already returns a Promise.
Conceptually, it works something like this:
function fetch(url) {
return new Promise((resolve, reject) => {
// Browser performs request
if (success) {
resolve(response);
} else {
reject(error);
}
});
}
The browser creates the Promise.
We simply consume it.
So What Does .then() Actually Do?
Consider:
fetch("/users")
.then(data => {
console.log(data);
});
Many beginners think .then() somehow retrieves the data.
That's not really what's happening.
.then() simply registers a callback function.
You're telling JavaScript:
"When this Promise successfully finishes, run this function."
Think of it like a food delivery app.
Order food
↓
Food arrives
↓
Call me
orderFood().then(eatFood);
The callback doesn't run immediately.
It waits until the Promise resolves.
Promise Chaining
Let's say we need:
- User data
- User posts
- User comments
A common approach is:
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts))
.then(comments => {
console.log(comments);
})
.catch(error => {
console.log(error);
});
This works perfectly.
But as applications grow, chains become harder to read and maintain.
Why Async/Await Was Introduced
Async/Await solves the readability problem.
Instead of:
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => {
console.log(posts);
});
we can write:
async function getData() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
console.log(posts);
}
This feels much closer to normal synchronous code.
The logic becomes easier to follow.
Does await Block JavaScript?
One of the most common interview questions.
Consider:
async function demo() {
console.log("A");
await fetch("/users");
console.log("B");
}
demo();
console.log("C");
Output:
A
C
B
Many developers initially expect:
A
B
C
But that's not what happens.
When JavaScript reaches:
await fetch("/users");
it pauses only the current async function.
The rest of the application keeps running.
So the flow becomes:
A
Pause async function
C
API finishes
Resume async function
B
This is why await does not block JavaScript.
It only pauses that specific function.
What Happens Under the Hood?
This code:
async function getData() {
const data = await fetch("/users");
console.log(data);
}
is roughly equivalent to:
function getData() {
return fetch("/users")
.then(data => {
console.log(data);
});
}
That's the key idea.
Async/Await is built on top of Promises.
The Promise mechanism still does all the work.
Async/Await simply provides a cleaner syntax.
Mental Models That Help Me Remember
Promise
The food is on the way.
fetch()
Place an online order.
Returns a Promise immediately.
.then()
When the food arrives,
call me.
await
I'll wait here for the food,
but everyone else can continue working.
async function
This function may contain waiting points.
Bonus: Quick Interview Answers !!
What is a Promise?
A Promise is an object representing the eventual success or failure of an asynchronous operation.
What does .then() do?
It registers a callback that executes when a Promise resolves successfully.
Why does fetch() work with .then()?
Because fetch() already returns a Promise.
Does await block JavaScript?
No. It pauses only the current async function.
Why was Async/Await introduced?
To make asynchronous code easier to read and maintain compared to Promise chaining.
Async/Await vs Promises?
Async/Await is syntactic sugar built on top of Promises.
Final Takeaway
Whenever I think about asynchronous JavaScript, I remember this:
Promise = Future Value
fetch() = Returns a Promise
.then() = Call me when ready
await = Pause this function, not JavaScript
async/await = Cleaner Promise syntax
Once these five ideas click, Promises and Async/Await become much easier to understand.
Top comments (1)
Did you find this post useful? Follow me for more developer-friendly content, share your thoughts in the comments, and help others discover it by sharing this post.