DEV Community

Cover image for how does javascript Promise work under the hood?
Sam aghapour
Sam aghapour

Posted on

how does javascript Promise work under the hood?

hello friends😀

I believe when we want to learn and conquer a programming language, we need to know how this language handles things under the hood to have a better understanding of what is going on and finally have fewer bugs when we use it.

If you are like me then let’s start a new journey to learn everything about promises and see how Javascript handles Promises under the hood.😁

Attention: if you don’t know anything about event loop, call stack, callback queue, and generally how javascript handles asynchronous operations under the hood which are this article’s Prerequisites, please learn them first, then come back to this article.
You can learn about these requirements by clicking on this link.

what are we going to investigate in this article:
1.Everything about promises and why and how they are handled with real-world and code examples.
2.Callbacks and callback hell
3.Why did async come into being
4.Microtask queue vs macrotask queue in javascript
5.Async / await syntax sugar
6.Debugging using try / catch / finally
7.Promise.all() vs Promise.allSettled()

Promises in javaScript

let’s start Promises with a real-world example:

Imagine there is a boy who will celebrate his birthday two weeks from now and his mother promises him to bake a cake for his birthday. In these two weeks, the mother prepares stuff to bake a cake and the boy is not going to sit all week and wait for cake and then prepare the other stuff for the birthday party:\ because this way everything would be deferred because of one cake and it’s ridiculous. So at the same time, the boy is preparing other things for the party. Now until the cake is not baked, the mother’s promise’s state is ‘Pending’. When it’s done, the Promise’s state is going to be one of two states:

  1. fulfilled or resolved
  2. rejected If the mother gets sick and can’t bake the cake (state: rejected), the boy is going to react (getting sad). But if the mother bakes the cake (state: fulfilled), the boy is going to react differently (getting happy), and no matter what state is rejected or fulfilled, the boy is going to have a party finally.

Image description
While the cake is being baked, the boy prepares other things for the party and this is an asynchronous operation because two things are happening at the same time.

Javascript is a single-threaded language and it is synchronous which means it just can execute codes line by line and should wait for the execution to get finished to go to the next line. So how does it execute asynchronous operations like ajax requests?🤔 There is where Promises came to the scene.

When javascript encounters ajax requests (like fetch), it knows that this is going to take a while to get the response, so javascript just returns an object until that data come and that this object is called Promise. In other words, javascript makes a promise that it is going to get something from the server as soon as possible and till that time it continues to execute the other lines instead of waiting for that data. now let’s check out what properties and methods this object includes:

Image description
In the above image, we can see that the data variable which is a fetch request returning a promise object. This object includes:
1.PromiseState property: its value can be one of three states:
+**“Pending” **when it is trying to get something from the server.

  • “fulfilled” when it gets data without an error.
  • “rejected” when it gets an error from the server. 2.PromiseResult property: its value gets changed depending on PromiseState value:

PromiseResult is undefined when the state is depending

PromiseResult is an error message when the state is rejected

PromiseResult is the response that gets from the server when the state is fulfilled

3. Prototype object: If you know about prototypes, then you have already guessed a prototype object is an object that consists of methods that ‘promise object’ inherited them. So these methods receive a function as a parameter (callback) and execute that function depending on promiseState property value:

  • .catch(): this method just executes its callback whenever the promiseState is ‘rejected’ and its callback receives a parameter which is the promiseResult value.
  • .then(): this method just executes its callback whenever the promiseState is ‘fulfilled’ and its callback receives a parameter which is the promiseResult value.
  • .finally(): this method executes its callback whenever the promiseState is either ‘rejected’ or ‘fulfilled’, in other words, if the PromiseState is not pending it executes the callback at the end anyway.

In our first real-world example, the boy’s sadness is like the catch method executing its callback. The boy’s happiness is like the then method executing its callback, and having a party no matter the cake is baked or not, is like the finally method executing its callback.

now let’s take a code example:

Image description
In the above example, it is pending at first, then gets an error from the server (promiseState = ‘rejected’) and .catch() method executes its callback, then .finally() method executes its callback.

Image description
In the above example, it is pending at first, then gets the data from the server successfully (promiseState = ‘fulfilled’) and .then() method executes its callback, then .finally() method executes its callback.

What are the callbacks? + how async operation came to being
In the above examples, we mentioned functions as the callback. So you may want to know what exactly is callback and why they exist?

JavaScript is a single-threaded language and that is why it can’t execute more than one line of code at the same time and executing more than one line of code at the same time means asynchronous operation. So JavaScript had to wait a long time to get a response from a fetch request, and it blocks the code obviously, and that is why callbacks came to the scene to make JavaScript able to do an asynchronous operation.
A callback is a function that is passed to a function as a parameter to get executed right after that function’s process is finished, and this is how asynchronous operation was born. By using a callback, javascript didn’t have to wait for something like an ajax request. Callbacks, get executed right after getting data from the server.

Attention: callbacks are not asynchronous and they are naturally synchronous. But they have the capability to enable JavaScript to handle async operations.

Let’s take an example of callbacks:

Image description
In the above example when the getData function was invoked, the second parameter (myCallback) is a function that is passed to getData as its callback, and it is going to execute that callback after getting a response from the fetch request.

Callback hell
The problem with callbacks that causes Promises to come to the scene is something called Callback hell.
Imagine if we wanted to do another async process inside a callback that was executed after the first async process and inside the second callback, we wanted to do another async process, and so on…

This would end in nested callbacks that are executed one after another and called callback hell.

Callback hell
In the above example, getData is my async function and I am calling it. After getting data, the callback is invoked and inside this callback, after logging the result, I invoke another async function as my second async function, and inside the second function’s callback, I keep doing the same process for 2 times more. As you can see I end up with nested callbacks that are hard to read and maintain. Imagine if I called more async functions inside callbacks. So I think you get the point :)
In promises, we don’t need to do it inside each callback and instead, we have a cleaner and more readable async handler thanks to .then() and .catch() methods.

Promise chaining

Image description
Well, we said .then and .catch methods came to help our code to be more readable and more manageable. But if we perform the callback hell example with these methods like above, you can see we are returning promise after promise and after promise…
And this chain of .then methods is called promises chaining. But what if there is something even much better than these methods that makes our code even more readable than it is now? :)

async / await syntax suger

Javascript introduced async / await in ES8 which is syntax sugar for promises, which means it uses promises, and the only difference between using async / await and .then / .catch methods is their syntax. async / await makes asynchronous operations more like synchronous operations so it helps code readability much more than those methods.

async / await
What is happening in the above example is the role of using async / await syntax:
1.The function which is an async operation should have an async word before it.
2.The async request should have an await word before it. This word stops the process inside the function(just inside) until the request is fulfilled or is rejected.
3.Whatever we do after the await line, is happening right after the request gets some result or error.

The getData function is asynchronous itself and returns a promise and if all the async requests inside it are fulfilled, we can execute the .then() method on the getData function and if requests are rejected we can execute the .catch() method on the getData function, although this is unnecessary to use these methods with async function if we don’t need to do something after all requests.

Image description

try / catch / finally blocks for debugging and catching errors

We can try our lines of codes and if there was an error we can catch it and either way we can do something finally:

try / catch / finally
In the above example, we put our requests inside the ‘try’ block and if there was an error, javaScript will stop continuing to execute codes inside the block and jump into the ‘catch’ block to show the error (the catch block receives a parameter which is the error) and after executing codes inside the catch block it will execute the ‘finally’ block. Even if there was no error, after the ‘try’ block it will execute the ‘finally’ block anyway.

Attention: when we use the try block we must use the catch block too. Otherwise, it’s not gonna work and throw an error, because after all, we are trying some codes to see if there is an error, catch it.
But the ‘finally’ block is not necessary.

These blocks help us debug our codes better and they fill in for .then() and .catch() and .finally() methods.

microtasks queue vs macrotasks queue

In the “How javaScript Asynchronous works under the hood?” article, we learned that all synchronous tasks go to the call stack and callbacks go to web APIs until their time come to be executed and when that time come, the callback goes to the callback queue. of course, callback queue has some other names including task queue and Macrotask queue which we call it macrotask queue in this article.
you might say,well, what is new about it? 🤔

there is another queue called microtask queue.😀I want to talk about this queue in this article because the microtask queue is related to promises and this is the right place to explore it.

The point is that all callbacks don’t go to the macrotask queue :
1.The callbacks that are scheduled like setTimeout and setInterval and event handler callbacks go to the macrotask queue.
2.The callbacks that are meant to be executed right after the asynchronous operation like callbacks of .then() .catch() methods, go to the microtask queue.

Now let’s see the priority of the event loop and which codes the event loop executes first:

  1. event loop first priority is call stack which consists of synchronous codes
  2. the second priority is the microtask queue which consists of promise callbacks
  3. the third priority is the macrotask queue which consists of scheduled callbacks the below gif shows these priorities very clear:

Image description
Now, let me ask you a question. What is the result of the code below?

Image description

The answer:
1.The first line goes to call stack,because it’s synchronous code.
2.Next line goes to web APIs and after 0 mili second, it goes to macrotask queue.
3.Next line goes to web APIs and after promise is resolved, it goes to microtask queue.
4.Next line is synchronous code again. so it goes to call stack.

Now event loop , executes the call stack tasks first which is “Start!” and then “End!”. now call stack is empty, so event loop executes the microtask queue’s callbacks which is “Promise!” and after microtask queue if this queue is empty, it is time for macrotask queue, so setTimeout callback gets executed which is “Timeout!”. let’s see the all operation in the gif below:

Image description

Promise constructor
There will be some times you want to instantiate a Promise object so in order to complete this article let’s just take a look at how it works:

Image description
In the above example, we instantiate a promise which is going to return ‘resolved data’ as a promiseResult with the fulfilled state.

Image description
In the above example, we instantiate a promise which is going to return ‘Error : rejected’ as a promiseResult with the rejected state.

Promise.all() vs Promise.allSettled()
In some cases, you may have an array of asynchronous requests that you want to take care of, all-in-one, and receive the array that includes the responses for each request. You can use the Promise.all() method that takes one parameter which is an array of requests and if all of that requests’s state is fulfilled, it returns an array of responses:
Image description

Now if just one of our requests is rejected, Promise.all() is going to return just an error of that rejected request. In other words, this method is ‘all or nothing’:

the secondData is going to be rejected
In order to fix this ‘all or nothing problem, dear javascript gives us another method called Promise.allSettled() which does the same process that promise.all does but the difference is allSettled method returns an array of objects for each request that includes two properties, ‘status’ which is the state of that request, and ‘value’ which is the result of that request, and ‘reason’ which takes the ‘value’ property’s place if the request is rejected. It is not going to give up all the responses just because one of the requests is rejected:

the secondData is rejected

This article ends here and hopefully you learned everything about promises and its complements in javaScript.

Goodbye and Good luck🤞

Top comments (0)