Advanced JavaScript: Working of JavaScript Engine

Sayan Sarkar
3 min readApr 24, 2021

It’s been a while since I posted any blog. Mainly due to lack of time from my job. But I’d like to make that up by providing some valuable insight. Today, we’re gonna look behind the scenes of how a JavaScript Engine works. This post assumes basic familiarity of JavaScript, on the reader’s end. Also, familiarity with concepts of asynchronous code execution would be handy. You could take a look at this article for a basic idea on async code. This post is for JavaScript developers, both novice and experienced,who want to understand such advanced concepts of JavaScript.

What is a JavaScript Engine??

A JavaScript Engine is essentially a program which executes JavaScript code. In the early days of JavaScript, it was primarily implemented as an interpreter , but nowadays it is also implemented as Just-In-Time(JIT) compiler which compiles JavaScript to bytecode. All modern browsers, as well as NodeJS(A Javascript Runtime) uses a JavaScript Engine. Google’s V8 Engine is one of the most famous and most used JavaScript Engines in the world. It is used by NodeJS as well as Google Chrome.

Working of JavaScript Engine

To understand working of JavaScript Engine, one needs to be familiar with the components associated with it:

  1. call stack: All operations to be executed in a JS code is pushed, in a sequence, onto the call stack. As each operation(block of code) gets executed, they are popped from the call stack
  2. browser API: Consists of DOM API’s, AJAX API’s and Timeout API’s(like setTimeout()). The relevant browser API is called whenever an async function is invoked from code. The execution of the async code is outsourced to the corresponding browser API.
  3. Message Queue/Event Queue: Any async operation(like setTimeout, AJAX calls, etc) has a callback(cb) function associated with it, which denotes what to do once the async operations completes. This cb function gets registered in the browser API’s(Or Posix Threads for NodeJS). When cb function is ready to execute (On completing the computation needed — if any — for the async operation), it gets sent back to the message queue
  4. Event Loop: Monitors the event/message queue for pending events and pushes them on to the call stack when time is allotted by the call stack. Event Loop synchronizes the call stack with the event/message queue.

Working:

  • Whenever any async piece of code is encountered, it gets passed onto the browser API(Or Posix/IO threads for NodeJS) for computing the result of that operation. The cb function of these async operations gets registered over here.
  • When the computation is completed, the result comes to the cb function/promise which is sent to the message queue.
  • Once the call stack allows us time, the callback function/promise which was registered in the event/message queue, is pushed into the call stack. This synchronization of the call stack with the event/message queue is done using the Event Loop.
  • The Event Loop monitors the message queue for pending callbacks and pushes it onto the call stack when it has time(or call stack is empty).
  • Once the results of the async code snippets complete their execution, they are popped from the call stack.
Source: https://miro.medium.com/max/985/0*jBHb1fFqLuoDyYCe.png

Queues:

The event queue/message queue mentioned above was just an abstraction of the actual queue based asynchronous implementation.

There are primarily 2 types of queues:

  • MacroTask Queue/Callback Queue:
    Let’s understand this with a basic example:
setTimeout(function cb(){
console.log("callback function");
}, 3000);

Whenever the above piece of code is encountered, the cb function gets registered in the setTimeout browser API or runtime env, along side a timer of 3 seconds. The rest of the JS code executes independently. On timer completion(timer is our async process result in this case), the cb function , which is ready for execution, is pushed onto the callback queue or the macrotask queue. Event loop checks availability of call-stack and pushes the cb function in the callback queue to the call-stack

Similar to setTimeout(), event Listeners and anything other than promises or mutation observers come to this callback/macrotask queue

  • MicroTask Queue: Similar to macro task queue, we have micro task queue. The difference is that events registered in the microtask queue takes priority(wrt execution) over macrotask queue events.
    Any cb functions returned via promises(API calls, or anything else) and mutation observers are pushed to this queue.

After the current task execution in callstack, the tasks in microtask queue are executed first, then the ones in macrotask queue

--

--

Sayan Sarkar

Senior Backend Engineer- System Design | Distributed System | Microservices | NodeJS