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.
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
5.Async / await syntax sugar
6.Debugging using try / catch / finally
7.Promise.all() vs Promise.allSettled()
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.
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?
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.
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.
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
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:
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
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?
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:
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’:
Goodbye and Good luck🤞
Top comments (0)