Even in this case, when we do not perform complicated tasks, the command console.log('World') will be executed only when the first one - console.log ('Hello') is completed. One thread, synchronous. However, my most beloved example is below, which although when looking at it, it seems that it should return the true, it does not.
So once again in a nutshell - one thread, synchronous.
After this thrilling introduction, which was really just a reminder, we are moving to ... another repetition of what I have already written in previous posts (Execution Context: Execution Phase, Hoisting, Execution Context: Creation Phase, Invocation, Execution Stack). Execution Context and Execution Stack (also known as Call Stack). The first one appears every time we invoke the function and has its information about for example variables. The latter is simply the stack on which the Execution Contexts of the called functions are pushed. In this case, however, no words depict it as well as a few lines of code.
Why so many console logs? Well, I think that nothing will present it better than just logging what happens at the moment. When we run the script, all variables and function statements will be stored in memory (hoisting ladies and gentlemen), and then the code will start to execute (Execution Phase). I will use chrome dev tools and put in a few breakpoints, thanks to which we will be able to pause the execution of the script at any time.
As you can see, I set them at the beginning and end of each function and the logs will inform us when the function code began to execute and when it ends.
As the code is executed from top to bottom, nothing will happen until the line 23 ... and then boom, the first console.log appears.
Here we have a blue arrow that shows us in which Execution Context currently executing code is. Ok, now let's get to the next breakpoint. Will it be in line 7?
Well, it turns out that we are already in the function two and nothing that was after the invocation of this function has been called. So…
... must wait. On the other hand, the Execution Context of function two lands on the stack.
In it, the function three is called and everything works the same as in the first case. The last console.log has to wait because we have arrived in the Execution Context of function three.
The matter here is simple. We do not invoke anything (in function three), so the whole thing is over now. In the console we have:
Ok, what about the rest of the code? Do we forget about it? Of course not. Since we will not create a new Execution Context at this stage, when everything is done in it, it will automatically be poped from our stack and...
... we will come back to …
So we are back in the Execution Context of function two, and it turns out that there is still something to do. The closing console.log is printed and as above, we pop the Execution Context from the stack. The last one remains.
Here, everything that's left is getting done.
And since everything has been done, the stack is empty! Phew, lots of pictures behind us so maybe now is the time for something that is not a repeat?!
I mentioned above that I wanted everything to be in one post but there is one more reason why I decided to do this "small" reminder. Now imagine that when visiting various websites, the example discussed above is the only way the websites operate. Something must be done for the next thing to be started. You probably agree that it would be very burdensome from the user's perspective. A good example is something like that.
In the above example, we have two functions. One is invoked on click (onClick) and calls setTimeout(). setTimeout in our case accepts two parameters. The first is the function (also called a callback function) that we want to invoke. The second tells how long will it take to invoke the passed callback. This time, clicking on the browser window will result in this:
In the above example, we have two functions. One is invoked on click (onClick), which implies setTimeout(). setTimeout in our case accepts two parameters. The first is the function (also called a callback function) that we want to invoke. The second tells how long will it take to invoke the passed callback. This time, clicking on the browser window will get something like that:
As the function waitFiveSeconds was temporarily released from the stack, "I was clicked!" appeared in the console. After 5s, the function waitFiveSeconds will be pushed from the web APIs to the task queue
Task queue is nothing more than a queue on which tasks are pushed. Nothing prevents you from queuing up more than one task. Of course, we do not want the asynchronous functions to be forgotten, so we have to somehow redirect them back to the stack. Fortunately, we do not have to solve it personally - if our stack is empty (meaning nothing is to be done, no execution context has been created) and our task queue is not empty, the first thing is pushed out of the task queue. As the function waitFiveSeconds was temporarily released from the stack, "I was clicked!" appeared in the console. After 5s, the function waitFiveSeconds will be pushed from the web APIs to the task queue.
The most common example of using setTimeout is when we set the second parameter to 0. After a slight change in the last code, what do you think will happen?
Precisely, the result is very similar to the previous one.
This is because function waitZeroSeconds has been postponed and will only be executed when our stack becomes empty. Considering that, after a click, our stack cannot be empty because the Execution Context of the function onClick lands on top of it. Only after everything that has been initialized in it is popped off the stack (in our case - console.log ('I was clicked')), the function waitZeroSeconds will be performed.
Fetch is a modern replacement for the XMLHttpRequest and takes place asynchronously. It is used to send requests to API to retrieve data.
The matter is simple. At the beginning in the console we get:
Since we know that fetch works asynchronously, we'll get a console.logs after the stack is cleared. The question is, however, in what order? (hint: check the number that indicates the amount of data fetched).
Everything nice and easy, right? Almost. In the example above, a thought arises - what if we would like to get the data first and use it in the second query? Let's take a look at another case.
Note that we do not know the id of the post and send the request to endpoint ... / posts / 1 to actually get it. Then we want to use the saved id and retrieve all comments belonging to this id.
Unfortunately, we did not succeed. This is due to the fact that what landed on our stack was a query without information about the id. Now we will modify the above code a bit.
Consolidating knowledge is the basis, so I describe the order once again.
- before getComments
- inside getId
- id in getComments function
- after getComments
- all comments
What can we do to properly get data about comments? There are few solutions but the newest / most popular is using async / await.
What are the Promises? First of all, these are objects, and secondly, they are quite specific objects.
Most importantly, they are simply promises that occur in a similar form as in everyday life. Each of us has promised something at some point of our lives. To the parents, that we will clean the room, the employer, that we will be on time in the office. Every promise has two stages. The first stage is the stage that I like to call transient. It looks like we made a promise that we'll take out the trash before the end of the day. Since we still have time, our promise is in the state
that is waiting for the final result. In this case, the value will be undefined. The next stage will tell us whether we were able to take out this unfortunate trash before the end of the day or not. If yes and our mission has been successful, then the status will be easy to guess -.
Here, for example, the value is simply an empty string. If, however, we have forgotten and we have failed to fulfill the promise, then the status will be (also an empty string used).
Depending on the status, different things can happen. Let's start with a simple example.
"What the hell is going on here" - you might ask? Well, through new Promise, we create a new object that accepts the callback function with two parameters - resolve and reject, which we later use depending on whether we are above or below 18 years. As you can easily guess, the resolve will serve us to handle the variant when we fulfill the promise and reject when we do not fulfill this promise. It seems pretty simple, right? Now let's move on. If you promise something then ... Well, then what? Here the keyword "then ()" sneaks in. Promises that are fulfilled will be a resolved promise object. "then ()" takes this value and uses a callback function to process it in some way. Let's modify the code a bit and then use then ().
Look. In then() we used a callback with the msg parameter. Because we already know that the promise will be fulfilled, msg will accept the value of the argument we have in resolve (). In our case, it will be a string - "I am old enough to ...". Thanks to that we will get
Now let's see what happens when we change the age in a variable to less than 18, let's say 17.
We got a mistake. This is because then() is used to handle your resolved promises. If, on the other hand, we want to catch a promise that we have not been able to fulfill (was rejected), we will use catch(). So let's add catch() to the code and keep the age of 17.
It will look like this. Of course, the principle is the same. In the error parameter, we get an argument but this time with reject() string "What a shame". Yes, the result will be:
As I suggested. Everything is fairly simple and transparent. Ok, let's add one promise..
As you can see, we have created the order function that will return us a Promise. It's the same Promise as the previous one, the only difference is that it's always resolved in this case, so its value is an argument in resolve(). But what more interesting is what is at the bottom. In the 17th line, we called the function order. Thanks to the fact that in then() we return Promise, we can use again then() (at the end we return the promise), but this time the result of fulfilling the promise will be the argument given in resolve (), which is in the function order.
So what about all of this? What does this give us? Well, thanks to this, we can chain Promises and pass the results from previous Promises to the next ones. Also, the result will always be passed (resolved or rejected Promises) which is very helpful in communicating with external APIs. I will modify the previous code a bit, add some functions that return Promises and chain them.
By capturing values in the parameters of callback functions, we can pass everything down the line. Let me also mention that it is worth remembering that we should always use catch() to be sure when something unexpectedly fails. Thanks to using Promises we do not have to call catch() for each function then(). It's enough to add 1 catch() at the end of any chain of Promises.
Ok, after this brief description of Promises, let's go back to async / await thanks to which we were able to firstly get the post's id and then use it to fetch further information from the API. So what does async / await do? Let's go back to the previous example.
Here we have an earlier code with some changes. We removed all then() and the code looks somehow nicer, right? Using await, we not only stopped further execution of the function's code but also obtained immediate access to the answer that Promise returns from fetch.