Skip to main content

Command Palette

Search for a command to run...

Promise Internals: What Actually Happens When You Call .then()?

You use .then() almost every day, but have you ever wondered what actually happens after you call it? Where is the callback stored, who executes it, and how does JavaScript know exactly when to run it? Let us look behind the scenes and understand how Promises really work.

Updated
•5 min read•View as Markdown
Promise Internals: What Actually Happens When You Call .then()?
B
An engineer figuring out one thing at a time!

Most JavaScript developers use Promises every day. But have you ever wondered what actually happens after you call .then()? Where does JavaScript keep your callback? Why does it not wait for the API to finish? Let us peek behind the scenes and understand the magic that makes asynchronous JavaScript work.

Have You Ever Thought About This?

Take a look at this code.

console.log("Start");

fetch("https://jsonplaceholder.typicode.com/users")
  .then(() => {
    console.log("Users loaded");
  });

console.log("End");

What do you think the output will be?

Start
Users loaded
End

Or

Start
End
Users loaded

If you guessed the second one, you are right.

But why?

After all, .then() comes before the last console.log().

To understand this, we first need to understand why Promises were introduced.


Life Before Promises

Before Promises, JavaScript relied heavily on callbacks.

Imagine you want to fetch a user, then fetch that user's orders, and finally calculate the total amount.

Your code would look something like this.

getUser(function(user) {
  getOrders(user.id, function(orders) {
    calculateTotal(orders, function(total) {
      console.log(total);
    });
  });
});

This works.

But as your application grows, callbacks become deeply nested and difficult to read.

This problem became famous as Callback Hell.

Promises solved this by making asynchronous code much cleaner and easier to understand.


What Exactly Is a Promise?

A Promise is simply an object that represents the result of an operation that will finish sometime in the future.

Think of ordering food online.

You place the order.

You do not receive the food immediately.

Instead, you receive a confirmation saying your order is being prepared.

That confirmation is like a Promise.

The actual food arrives later.


Every Promise Has Three States

Every Promise starts in the Pending state.

Eventually, it changes to one of two final states.

  • Pending means the operation is still running.

  • Fulfilled means the operation completed successfully.

  • Rejected means something went wrong.

Once a Promise becomes Fulfilled or Rejected, it can never change again.

Think of a courier package.

It can be "Out for Delivery."

Later it becomes either "Delivered" or "Delivery Failed."

It never goes back to "Out for Delivery."


What Actually Happens When You Call .then()?

This is where things become interesting.

Consider this example again.

console.log("Start");

fetch("https://jsonplaceholder.typicode.com/users")
  .then(() => {
    console.log("Users loaded");
  });

console.log("End");

Here is what happens behind the scenes.

Step 1

JavaScript executes

console.log("Start");

Output

Start

Step 2

fetch() starts the network request.

This request is handed over to the browser.

JavaScript does not wait for it to finish.

Instead, it immediately moves to the next line.


Step 3

When JavaScript sees.then(), it does not execute the callback immediately.

Instead, it simply remembers it.

It is almost like saying,

"Whenever this Promise finishes successfully, please execute this function."


Step 4

JavaScript continues executing the remaining code.

console.log("End");

Output

Start
End

Step 5

Some time later, the browser finishes the network request.

The Promise changes from Pending to Fulfilled.

Now JavaScript places the callback inside the Microtask Queue.

Notice something important.

The callback is not executed immediately.

It is only scheduled.


Step 6

Once the Call Stack becomes empty, the Event Loop checks the Microtask Queue.

Since our callback is waiting there, JavaScript finally executes it.

Users loaded

Final output becomes

Start
End
Users loaded

Why Does .then() Feel Instant?

A common misconception is that JavaScript keeps checking whether the Promise has finished.

That is not true.

The Promise itself remembers every callback attached using .then().

The moment the Promise is fulfilled, JavaScript automatically schedules those callbacks.

There is no continuous checking happening in the background.

This makes Promises efficient and lightweight.


Promise Chaining

One of the biggest advantages of Promises is chaining.

fetch("https://jsonplaceholder.typicode.com/users")
  .then(response => response.json())
  .then(users => users[0])
  .then(user => console.log(user.name))
  .catch(error => console.error(error));

Each .then() receives the value returned by the previous one.

This creates a clean flow without deeply nested callbacks.

If any step fails, execution immediately jumps to .catch().


Final Thoughts

Promises are much more than a cleaner way to write asynchronous code.

When you call .then(), JavaScript does not pause execution or repeatedly check whether the operation has finished.

It simply stores your callback, continues executing the remaining code, and schedules it to run once the Promise is fulfilled.

Once you understand this flow, concepts like async and await, the Event Loop, and React's asynchronous rendering become much easier to understand.

The next time you write .then(), you will know exactly what is happening behind the scenes.

More from this blog

B

Bhavy Ladani | Blogs

22 posts

Practical articles that simplify JavaScript, React, browser internals, web security, and AI engineering. Every article focuses on helping developers understand what actually happens behind the scenes.