how does javascript Promise work under the hood?
Sam aghapour
Posted on March 27, 2022
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:
- fulfilled or resolved
- 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.
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:
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:
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:
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.
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:
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.
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
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.
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.
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:
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:
- event loop first priority is call stack which consists of synchronous codes
- the second priority is the microtask queue which consists of promise callbacks
- the third priority is the macrotask queue which consists of scheduled callbacks the below gif shows these priorities very clear:
Now, let me ask you a question. What is the result of the code below?
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:
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:
In the above example, we instantiate a promise which is going to return âresolved dataâ as a promiseResult with the fulfilled state.
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:
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â:
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:
This article ends here and hopefully you learned everything about promises and its complements in javaScript.
Goodbye and Good luckđ€
Posted on March 27, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.