DEV Community

Cover image for JavaScript Promises & Under-the-Hood Mechanics
Arunish
Arunish

Posted on

JavaScript Promises & Under-the-Hood Mechanics

Promises, while simple in idea and concept, the way they are written or implemented in JavaScript is slightly confusing to many beginners or even experienced developers who come from other languages.

Let's look at a typical asynchronous function-

async function increment_val(val = 0) {
  const val1 = get_base_value(); 
  const val2 = Number(await get_user_input()); // asynchronous task
  const final_val = val + val1 + val2;
  return final_val;
}
Enter fullscreen mode Exit fullscreen mode

I've written this intentionally in a verbose way to make our discussion easier. Now, at first glance, it may look like the function is returning whatever the final_val is. But actually, this function is returning a Promise.

Any function with an async keyword at the beginning makes that function return a Promise. Even if there is no explicit return.

To understand the concept better, let's consider a relatable real world analogy. Suppose you go to a coffee shop and place your order at the counter. After payment, the cashier gives you a receipt and asks the barista to prepare your brew.

An important point to note here is that, after billing is performed, you are immediately given a receipt (the Promise), and not the actual coffee (the Result).

It will take some time for your coffee to arrive, but you don't have to wait and block the queue. You are free to go and do other things, look around the shop, check emails etc. while your order is fulfilled.

A Promise in JavaScript is exactly the same.

When the execution reaches our increment_val() function, it returns immediately with a Promise (the receipt). And not with the actual return final_val (the coffee) yet.

The promise will be "fulfilled" later. It will either resolve with that returned final_val or reject if an error is encountered (like if the user input is NaN).

What does the await do here?
We use the await keyword to get the fulfilled value of a promise. You can only use await inside an async function.

Why we even need all these? Let's take a look under the hood

In the small increment_val() function we considered above, there is a task in that function which has to get an input value from the user.

Since we don't know when the user will input that value, we have 2 options-

  1. Wait till the user inputs a value. This will make the JavaScript engine pause and not attend to other tasks. OR
  2. Make the code block asynchronous. In the example above, we did this by writing async keyword in front of the function.

When the JavaScript engine executes our async increment_val() function and it encounters await get_user_input()-

  1. It saves the state of that function (all local variable values and its lexical scope).

  2. Pauses further execution of that function only and exits the function context by returning with a Promise.

Meanwhile, the JavaScript engine returns control to the event loop and is now free to process other tasks.

Once the promise returned by get_user_input() is fulfilled, the JavaScript engine is notified. Who notifies it? The browser environment running outside it.

The JS engine finishes whatever immediate task it was doing, comes back to the increment_val() function, loads its saved state and resumes operations right where it left off.

A very important point to note here is, while the JS engine (where our code logic runs) is synchronous and single threaded, the browser environment where it lives is not.

Many tasks are performed by the surrounding browser environment and even the underlying OS (like fetching network requests, timers, or disk I/O). These environments can be asynchronous and multi-threaded.

The JS engine can offload many of its tasks to these environments. We often implement this by using the tools provided by these environments through Web APIs like setTimeout, Fetch, CookieStore, etc.

This is how the JavaScript engine, despite being single-threaded and synchronous itself, handles asynchronicity. It has essentially offloads the heavy lifting to the browser environment, while holding a Promise or "receipt" until the job is done.


Understanding that async functions always return a Promise and that await is simply a mechanism to pause execution and offload work to the browser environment makes debugging complex applications much easier.

There are some other ways of returning promises as well. Like using the constructor new Promise((resolve, reject) => { ... }), however using async/await syntax I find is easier to implement as they are written similar to other synchronous functions.

Thanks for reading!

p.s. If you liked this article, check out my blog NewGen Stack where I cover more modern web development topics and tools for content creation.

Top comments (0)