DEV Community

Rajat Oberoi
Rajat Oberoi

Posted on

Understanding the Event Loop, Callback Queue, and Call Stack & Micro Task Queue in JavaScript

Call Stack:

  • Simple Data structure provided by the V8 Engine. JS Engine contains Memory Heap and Call Stack.
  • Tracks execution of our program, by tracking currently running functions.
  • Our complete Js file gets wrapped in main() function and is added in Call Stack for execution.
  • Whenever we call a function, it's gets added to Call Stack. Once it gets executed and finishes it gets popped out from stack.
  • JS is single threaded i.e. Single Call Stack
//Basic Example

const x = 1;
const y = x + 2;
console.log('Sum is', y);

/*

- This code gets wrapped in main() and main is added to Call Stack.
- log('Sum is 3') added to call stack.
- On console we would get 'Sum is 3'. Now log function is finished and gets removed from Call Stack.
- Now end of script, main function gets popped out of Call Stack.
*/
Enter fullscreen mode Exit fullscreen mode

const listLocations = (locations) => {
    locations.forEach((location) => {
        console.log(location);
    });
}

const myLocation = ['Delhi', 'Punjab'];
listLocations(myLocation)

Enter fullscreen mode Exit fullscreen mode
  1. Main function gets pushed onto the call stack.
  2. Line 1 we are declaring the function but not calling it, hence it will not get added to call stack.
  3. Line 7 we are defining our location array.
  4. Line 8 Function call, So it is going to be pushed to call stack and is the top item there.
  5. listLocations will start running. pushed to call stack.
  6. forEach is a function call so gets added to call stack. forEach calls anonymus function one time for each location.
  7. anonymous('Delhi) function gets added to call stack with argument Delhi.
  8. Now console.log function gets added to call stack. It prints Delhi, and finishes. and pops out.
  9. anonymous('Delhi) finishes and pops out.
  10. forEach is not done yet hence does not pops out. anonymous('Punjab) gets added to call stack.
  11. Now console.log function gets added to call stack. It prints Punjab, and finishes. and pops out.
  12. forEach is completed and hence poped out of call stack.
  13. listLocations is done, hence pops out.
  14. Script is completed. main() pops out.

Callback Queue

It's job is to maintain a list of all of the callback functions that needs to be executed.

console.log('Starting Up!');

setTimeout(() => {
    console.log('Two Seconds!');
}, 2000);

setTimeout(() => {
    console.log('Zero Seconds!');
}, 0);

console.log('Finishing Up!');

Enter fullscreen mode Exit fullscreen mode
  1. main() pushed to call stack.
  2. Line 3: setTimeout pushed to call stack. < setTimeout is not part of JS V8 but is part of NodeJS. It's implementation is in C++ provided by NodeJs.
  3. setTimeout when called registers an event which is an event-callback pair. Event here is wait 2 seconds and callback is the function to run. Another example of event-callback pair is wait for database request to complete and then run the callback that does something with the data.
  4. This new event i.e. setTimeout function is popped and is registered in Node APIs. 2 Seconds clock starts ticking down.
    While waiting for those 2 seconds we can do other stuff < Non Blocking nature of node >

  5. Line 7: setTimeout registers another event in Node API.

  6. Now timeout 0 seconds are up, now the callback needs to be executed.

Callback Queue comes in picture: It's job is to maintain a list of all of the callback functions that needs to be executed. Front item gets executed first.

  1. callback of setTimeout with 0 seconds timeout gets added to queue so that it can be executed. But to get executed it needs to be added on Call Stack, that's where function go to run.

Now, here Event Loops comes in picture, it looks at the call stack and callback queue, If call stack is empty then it will run items from callback queue. < This is the reason 'Finishing Up!' logged before 'Zero Seconds!' as main was in call stack, event loop is waiting for main to get popped out>

  1. log('Zero Seconds!') gets added to call stack. and message is printed on console.
  2. main is completed and pops out.
  3. Event loop takes item from call back queue and push to call stack. 'Zero Seconds!' prints.
  4. Once 2 seconds achieved, callback('Two Seconds!') added to callback queue, moves to call stack, gets executed. 'Two Seconds!' prints.
  • The delay specified in setTimeout is not the exact timing of execution but rather the minimum delay after which the callback can be added to the callback queue.
  • The actual execution time depends on the event loop's scheduling and the availability of the call stack. This asynchronous behaviour allows JavaScript to handle non-blocking operations effectively, especially in environments like Node.js where I/O operations are common.

Non-Blocking Nature of Node.js

  • JavaScript is single-threaded, meaning only one function can be executed at a time.
  • However, Node.js and the browser environment manage asynchronous tasks using other threads.
  • While the call stack is executing synchronous code, the environment handles asynchronous events in the background.

Summary

  • Call Stack: The structure that keeps track of function calls. Only one function can run at a time.
  • Callback Queue: A queue that holds callbacks that are ready to be executed.
  • Event Loop: A mechanism that checks if the call stack is empty and if so, pushes the next callback from the callback queue to the call stack.

Micro Task Queue

  • When working with Promise, NodeJS works with micro task queue.
  • Microtasks are queued for execution.
  • When a Promise is resolved or rejected, its .then() or .catch() callbacks are added to the microtask queue.
  • In async await: When await is used inside an async function, it essentially breaks the function into two parts:

  • Synchronous Part: The part before the await keyword executes synchronously.

  • Asynchronous Part: The part after await executes asynchronously once the awaited promise resolves.

  • Microtasks come into play when promises are resolved inside async functions using await. After the awaited promise resolves, the callback (or subsequent async code) following the await is placed in the Microtask Queue for execution.

  • Event Loop prioritise the microtask queue. Microtasks have higher priority than macrotasks (such as setTimeout callbacks or event handlers), which means they are executed as soon as the call stack is empty and before the event loop moves to the next macrotask.

  • First micro task queue is emptied then event loop moves to callback queue.

  • After each task picked from callback queue and pushed to call stack, event loop will check micro task queue.

Top comments (0)