DEV Community

aarthirs
aarthirs

Posted on

JavaScript Promises & Async/Await Explained with a Real-Life Example 🍕

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/await introduced?
  • Does await block 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");
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

Instead, I often see:

fetch("/users")
  .then(...)
Enter fullscreen mode Exit fullscreen mode

The reason is simple:

Most modern APIs already return Promises for us.

For example:

fetch("/users");
Enter fullscreen mode Exit fullscreen mode

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);
    }

  });
}
Enter fullscreen mode Exit fullscreen mode

The browser creates the Promise.

We simply consume it.


So What Does .then() Actually Do?

Consider:

fetch("/users")
  .then(data => {
    console.log(data);
  });
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
orderFood().then(eatFood);
Enter fullscreen mode Exit fullscreen mode

The callback doesn't run immediately.

It waits until the Promise resolves.


Promise Chaining

Let's say we need:

  1. User data
  2. User posts
  3. 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);
  });
Enter fullscreen mode Exit fullscreen mode

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);
  });
Enter fullscreen mode Exit fullscreen mode

we can write:

async function getData() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);

  console.log(posts);
}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Output:

A
C
B
Enter fullscreen mode Exit fullscreen mode

Many developers initially expect:

A
B
C
Enter fullscreen mode Exit fullscreen mode

But that's not what happens.

When JavaScript reaches:

await fetch("/users");
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

is roughly equivalent to:

function getData() {
  return fetch("/users")
    .then(data => {
      console.log(data);
    });
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

fetch()

Place an online order.
Returns a Promise immediately.
Enter fullscreen mode Exit fullscreen mode

.then()

When the food arrives,
call me.
Enter fullscreen mode Exit fullscreen mode

await

I'll wait here for the food,
but everyone else can continue working.
Enter fullscreen mode Exit fullscreen mode

async function

This function may contain waiting points.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Once these five ideas click, Promises and Async/Await become much easier to understand.

Top comments (1)

Collapse
 
aarthirs profile image
aarthirs

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.