Event Loop in Node.js
Overview
Javascript is a single-threaded and synchronous programming language which means only a single thing can happen at a time, but some tasks in javascript can run in parallel due to its asynchronous nature. Event Loop plays a major role in the execution of asynchronous functions. The event loop plays a crucial role in scheduling which operations our only thread should be performing at any given point in time. Hence the event loop helps us to understand Node's asynchronous processes and its non-blocking I/O nature.
Pre-requisites
Before diving deep into the concept of Event-driven programming and understanding the working of the event loop in Node.js, you should be familiar with some basic terminologies like the call stack, WebAPI's, callback queue, microtask queue, and threads.
-
Call Stack : It is a data structure where all the subsequent function invocation or new execution context is pushed to this call stack. Call stack works on the principle of Last In, First Out, i.e. the last statement pushed will be popped out first. It keeps the record of which statement is currently being executed as the current statement is pushed to the top of the stack. Once the execution context is done with a task, that task is popped out.
-
Heap : is the memory (place) where objects are stored randomly when we define variables in our program.
-
Web API's : The browser has some additional functionalities like Local storage, Timer, Address field, etc. and these functionalities and for incorporating those additional functionalities and connecting to the outer environment like localStorage, fetching data from a remote source, etc., the browser comes up with the Web API's. Any JS file running inside the browser will get access to these Web API's through the global object. When we use web APIs, we utilize an event table. The event table keeps track of callbacks for their respective events. When an event happens, i.e. an asynchronous operation is completed, then the callback of that event gets enqueued into the callback queue.
-
Callback/ Macrotask Queue : It is also known as, Event Queue or Message Queue. This queue contains the methods from the event table. The function is passed to the call stack with the help of the event loop in Node.js as soon as the call stack becomes empty. Some Macro-Tasks are setInterval, setTimeout, and setImmediate.
-
Microtask Queue This is precisely similar to the callback queue but with much higher priority. All callback functions coming through promises and mutation observers will go into the microtask queue. As the priority of the microtask queue is more than the callback queue, the methods in the callback queue cannot get a chance to enter the call stack if the microtask queue is long, which leads to starvation of tasks inside the callback queue. Some Micro-Task are Promise callback and process.nextTick.
-
Threads : It is a coding sequence that resides within the process and uses its memory pool. Whenever we execute a Node.js file, a single thread is automatically created. This thread is the only location where our entire program is going to be executed, and inside of which, an event loop is generated.
-
Input/Output (I/O) : Any task that involves use of external hardware like network operations, file system, etc.
Introduction
The earlier websites were mainly static, and the data displayed by those websites was in HTML form which was not very interactive. Presently, the websites have become very user-friendly (using javascript) and include various intensive operations like fetching data from any external API. To handle these types of operations, there is a model called the Asynchronous programming model in Nodejs.
Since javascript is a single-threaded language, it means all the operations are executed by a single thread. Javascript has a single call stack which also means the execution of statements takes place one step at a time. In this scenario, if we want to fetch data from any API, it can take any amount of undetermined time, which can depend on Network speed, Server availability, and connection traffic. Fetching data is a processing-heavy function; when the fetchAPI() function is added to a call stack, so the js engine would have to wait for a long amount of time to move to the next statement.
This is known as blocking nature. Most of the browsers do not wait for much longer and will give a pop-up showing that the Page is Unresponsive, asking if you want to close the web page.
While the stack is blocked by a processing-heavy function, Users will not be able to interact with the application in any form as the Node.js runtime environment only has a single thread and can only process one statement at a time.... or is there something more to this?
There is more to the above scenario; there are many C/ C++ API's asynchronous (I/O) input/output and interactions with the operating system (OS) that allow the execution of code in a multithreading threading manner without the shortcomings of the memory. The Event Loop in Node.js was designed to aid with the interactions between the main application thread and asynchronous components.
Event-Driven Programming
An event loop in Node.js is an event-listener that operates inside the Node.js environment and is always ready to attend, process, and output an event. Event-driven programming is a programming paradigm in which the program flow depends on the actions performed by the user. It helps avoid complexity and collision issues.
Event-driven programming is based on the following concepts :
- Event Emitter that raises the happening of an event.
- Event Loop that listens for event triggers and calls the required event handler.
- Event Handler is a function that performs some actions for a particular event happening.
A real-world example of Event-Driven Programming for viewers that are at any level of programming experience, would be the web browser. Every time you interact with a website through it's interface, like scrolling or clicking a button to open a new link etc.; an event is happening. These events have an associated callback function with it, and when triggered, they are executed by the event loop in Node.js.
Event-driven Programming makes use of the following notions:
- An Event Handler(that is a callback), will be called when an event is triggered.
- The Main Event loop listens for event triggers and calls the associated event handler for that event.
Benefits of Event Emitters in Node.js
- Provides better scalability and fault tolerance.
- It helps in providing asynchronous and reactive systems.
- It reduces CPU consumption and bandwidth usage.
- It allows excellent separation of responsibility without stopping components from working together.
Features of Event Loop in Node.js
The javascript engine is responsible for running a single piece of code of a program at any given point. So, who instructs the javascript engine to execute any async pieces of code? In reality, the javascript engine runs inside a hosting environment, which is Node.js or any web browser.
Nowadays, javascript is embedded into all kinds of devices, from remote to light bulbs. Every type of that device has a different type of hosting environment for the JS Engine. The common denominator in all those environments is a built-in mechanism called the event loop, which handles the execution of the asynchronous code of your program.
For example : When your Node.js program makes an asynchronous request to fetch some data from a server, you code the response part in a callback function, and the javascript engine tells the hosting environment that the execution of this function is suspended for now, but whenever the request is completed, please call this function back.
The browser then waits for the response from the network request, and when it is completed, it will prepare the callback function to be completed by inserting it into the callback queue.
Thus the Event loop in Node.js has only one job - to monitor the call stack and the macrotask queue, and the microtask queue. If the event loop finds the call stack is empty, it will take the first event from the callback queue and push it into the call stack, which executes the event. Every iteration of such a process is known as a Tick in the event loop.
Features of Event loop in Node.js in brief :
- The Event loop is an endless loop that waits for the events and executes them by pushing tasks into the call stack.
- The event loop continuously monitors the call stack, and when the call stack is empty, the event loop pushes the task from the callback queue to call the stack.
- The event loop basically helps us the javascript engine in the execution of the asynchronous callbacks and promises.
- The event loop executes the oldest task first.
- The event loop in node.js gives priority to the microtask queue more than macrotask queue.
Become a confident backend developer with our Node.js Free course. Enroll now and learn to architect and implement robust server-side solutions.
How does an Event Loop in Node.js Works?
Let's take up examples and understand the working of the Event loop in Node.js
Output :
Let’s execute the code given above and see what happens:
Step 1 : At initial state, the call stack is empty, and the console is also clear. Now the whole code will run line by line.
Step 2 : At line 1, console.log() is encountered by the javascript engine, and it is pushed into the call stack.
Step 3 : The statement Hello the execution has started gets logged into the console, and the console.log() is popped out from the call stack. Step 4 : At line 3, the setTimeout() is encountered by the javascript engine and setTimeout() is pushed into the call stack.
Step 5 : Now the setTimout() is executed. The javascript engine takes the help of the web API's and gets access to the timer feature and starts a timer for 5000 ms. Also, a callback (cbfunc1) function is registered in the web API's environment.
Step 6 : Now the javascript engine moves to the next line without waiting for anything, and setTimout() is removed from the call stack.
Step 7 : At line 7, another console.log() is encountered by the javascript engine, and it is pushed into the call stack.
Step 8 : The statement Execution finished gets logged into the console, and the console.log() is removed from the call stack.
Step 9: Now, atleast 5000 ms later, after the timer expiration, the callback (cbfunc1) funtion moves into the Macrotask queue.
Step 10 : The event loop in Node.js checks the status of the call stack and pushes the callback (cbfunc1) function from the Macrotask queue to the call stack.
Step 11 : The callback (cbfunc1) function is executed and the console.log() inside (cbfunc1) gets added into the call stack.
Step 12 : The console.log() of line 4 gets executed and is popped out of the call stack.
Phases of the Event Loop in Node.js
The Event Loop is comprised of the following six phases, which are continuously repeated if there is any piece of unexecuted code.
- Timers
- Pending (I/O) Callbacks
- Waiting / Preparation
- I/O Polling
- setImmediate() callbacks
- Close events
Phase 1 : The callbacks of timers in javascript (setInterval, setTimeout) are kept in a heap memory until they expire. If there are any callbacks associated with any expired timer in the heap memory, the event loop in Node.js executes them in ascending order of delay time. However, the execution of the callbacks is controlled by the Poll phase of the event loop.
Phase 2 : In this phase, the event loop in Node.js executes system-related callbacks. Let us say we make a node server and run it on a port, but that port is used by some other process. Node.js will throw an error ECONNREFUSED. Some of the systems may want the callback to wait for completion due to some other tasks that the OS is processing. Thus these callbacks are added to the pending callback queue for execution.
Phase 3 : During this phase, the event loop does nothing much. The event loop is idle and generally gathers information and plans what needs to be executed during the next phase. There is no mechanism that could guarantee code execution during this phase.
Phase 4 : During this phase, the event loop in Node.js watches out for new async I/O callbacks except the setImmediate, setInterval, setTimeout, and other closing callbacks. The event loop in Node.js does two things in this phase:
- If there are some remaining callbacks in the poll phase queue, it will execute those until the queue is empty.
- If there are none, the event loop will stay in the poll phase for some time. This time depends on the following:
- The first case is when there are callbacks in the setImmediate queue, the event loop will not stay for a long time and will go to the next phase. Again, it will start executing the callbacks until the check phase callback queue is empty.
- The second case is when the event loop gets to know there are some callbacks associated with an expired timer that are waiting to be executed. The event loop will move to the next phase, i.e. Check/setImmediate and then to the Closing callbacks phase.
Phase 5 : During the phase, the event loop in Node.js takes the callbacks from the Check phase's queue and starts executing until the queue is cleared. The event loop in Node.js will come to this phase when there are no callbacks remaining poll phase.
Phase 6 : During this phase, the event loop in Node.js executes the callbacks associated with the closing events like process.exit() or socket.on('close', fn).
Conclusion
- The event Loop in Node.js aids with the interactions between the main thread and asynchronous components of the program.
- An event loop in Node.js is an event-listener that operates inside the Node.js environment.
- Event-driven programming consists of an event handler and an event loop for triggering the respective event handler.
- The browser provides the javascript engine with web API's for additional functionalities like Local storage, Timer, Address field, etc.
- The Event table keeps track of callbacks for their respective events.
- The Event loop is an endless loop that waits for the events and executes them by pushing tasks into the call stack.
- There are many C/ C++ API’s for interactions with the operation system (OS) that allow the execution of code in a multithreading threading manner.
- The event loop in Node.js is comprised of six phases that are: timer, pending callbacks, waiting, polling, setImmediate() callbacks, and closing events.
- Every iteration of such a process is known as a Tick in the event loop.