Let us understand all of these with example.
The primary function of the call stack is to run one piece of code at a time, therefore anything contained therein is executed without any context.
Step1: logs ‘First line’
from line 2–4 function declaration is stored and allocated a memory.(during Hoisting)
Step 2: greet function is invoked; at this point execution context is created for ‘greet()’. And line by line code is executed.
Then it logs ‘Hello! world’
Step 3: it logs ‘End line’
Step 4: control moves and sees for any line of code to be executed and finds nothing and hence GEC also pops from call stack.
No doubt the above code and execution cycle is synchronous.
Let’s go on to the next level. What if we want to set up a timer⏰ and once the timer expires, we want to run a certain code? Basically an asynchronous code.🤔
You must have guessed the output just by reading this code 😃 ; now let’s see how it works.
Step 1: logs ‘First line’
Step 2: controls move to setTimeout, greetCallback() is then get registered and starts its timer, minimum of 10000ms.
Note: timer set in setTimeout is never precise, it is never guaranteed that callback will run at the specific time that is set. Definitely, callback may take longer than the time set, for execution.
Step 3: (Still, the timer is not over.) control reaches line 5, logs ‘Last line’.
Step 4: nothing to execute in the next line and hence GEC also pops out from call stack.
Post 10000ms → timer expires
Step 5: We are left with greetCallback for execution, greetCallback() was registered with web APIs, however, it cannot be put directly into the call stack to run the code.
When the timer ends, first and foremost, the registered callback moves to the callback queue.
Step 6: Event loop check if any callback is present in callback queue, if yes, then it moves the callback to the call stack for execution.
Callbacks from web APIs will get pushed into the callback queue and wait for their turn for execution. Some web APIs examples are setInterval, clearTimeout, DOM APIs, etc.
In our example, when greetCallback moves to the callback queue, the event loop first checks if any code needs to run; if yes, it then also checks if the call stack is empty or not. Once the event loop confirms the call stack is empty, it first pushes GEC in call stack.
Next, the event loop dequeues callback from callback queue and pushes code into the call stack for execution.
The event loop examines the callback queue one more time but finds nothing; and hence GEC is popped out at the end.
Till the timer is okay, but we also use fetch web API very frequently to interact with a server. Now let’s look at an example where we would need to use both the timer and fetch web API.
Let’s look at the log first.
In this example, we see fetch and setTimeout, which are both registered in web APIs. However, fetch callback moves to microtask queue and once the timer expires, setTimeout callback moves to callback queue.
When the event loop checks, it first looks for callbacks in the microtask queue, which has a higher priority than the callback queue. As we can see in Fig. 3, the event loop first pushes the callback from the microtask queue to the call stack for execution.
Once all callbacks are completed from microtask queue, event loop then checks callback queue, if any piece of code needs to execute. If yes, it pushes all callbacks one by one from the callback queue to the call stack for execution.
Microtask queue contains all the web APIs that are blocking in nature. Which means the main thread gets blocked for a certain time, till all blocking callback gets executed. Some blocking web APIs are Promise, IntersectionObserver, MutationObserver, etc.
Note: Callbacks in the callback queue can also go into starvation if callbacks keep on queuing forever in the microtask queue.