Illustration of Event Loop in Node.js

Illustration of Event Loop in Node.js

Understanding Asynchronous Programming and Concurrency

What is an Event Loop:

The event loop is a fundamental part of JavaScript's runtime environment, responsible for handling asynchronous operations and managing the execution of code. It ensures that JavaScript remains non-blocking and efficient in handling tasks such as timers, callbacks, and I/O operations. The event loop consists of several phases, each with its own responsibilities.

Event loop in JavaScript is a mechanism through which the 'calls waiting for execution' in the callback queue/job queue can be put on the call stack. For any event from the callback queue/job queue to come to call stack, the call stack will have to be empty.

Phases involved in Event Loop:

Event Loop Phases

  1. Timers phase: In this phase, the event loop checks if any timers have expired. Timers are scheduled using functions like setTimeout and setInterval. If a timer has expired, its associated callback function is added to the task queue.

    Example:

     setTimeout(() => {
       console.log('Timer callback executed');
     }, 2000);
    
  2. I/O callbacks phase: This phase handles I/O events such as network requests, file system operations, or user input. When an I/O event occurs, its corresponding callback function is added to the task queue.

    Example:

     const fs = require('fs');
     fs.readFile('example.txt', 'utf8', (err, data) => {
       if (err) throw err;
       console.log(data);
     });
    
  3. Idle, prepare phase: These phases are less commonly used in typical JavaScript applications. The idle phase is responsible for handling callbacks with no associated I/O events or timers, while the prepare phase is for internal use by the event loop.

  4. Poll phase:

    The poll phase has two main functions:

    1. Calculating how long it should block and poll for I/O, then

    2. Processing events in the poll queue.

When the event loop enters the poll phase and there are no timers scheduled, one of two things will happen:

  • If the poll queue is not empty, the event loop will iterate through its queue of callbacks executing them synchronously until either the queue has been exhausted, or the system-dependent hard limit is reached.

  • If the poll queue is empty, one of two more things will happen:

    • If scripts have been scheduled by setImmediate(), the event loop will end the poll phase and continue to the check phase to execute those scheduled scripts.

    • If scripts have not been scheduled by setImmediate(), the event loop will wait for callbacks to be added to the queue, then execute them immediately.

Once the poll queue is empty the event loop will check for timers whose time thresholds have been reached. If one or more timers are ready, the event loop will wrap back to the timers phase to execute those timers' callbacks.

Example:

    const http = require('http');
    const server = http.createServer((req, res) => {
      res.end('Hello, World!');
    });
    server.listen(3000);
  1. Check phase: This phase allows a person to execute callbacks immediately after the poll phase has completed. If the poll phase becomes idle and scripts have been queued with setImmediate(), the event loop may continue to the check phase rather than waiting.

    setImmediate() is actually a special timer that runs in a separate phase of the event loop. It uses a libuv API that schedules callbacks to execute after the poll phase has completed.

    Generally, as the code is executed, the event loop will eventually hit the poll phase where it will wait for an incoming connection, request, etc. However, if a callback has been scheduled with setImmediate() and the poll phase becomes idle, it will end and continue to the check phase rather than waiting for poll events.

    Example:

     setImmediate(() => {
       console.log('setImmediate callback executed');
     });
    
  2. Close callbacks phase: This phase handles the closing of resources, such as closing database connections or terminating server connections. The associated callback functions are executed in this phase.

    Example:

     const server = http.createServer((req, res) => {
       // ...
     });
     server.close(() => {
       console.log('Server closed');
     });
    

The event loop continuously iterates through these phases, checking for pending tasks in each phase and executing their corresponding callbacks. It ensures that JavaScript code can handle both synchronous and asynchronous operations efficiently, without blocking the execution of other tasks. By managing the event-driven nature of JavaScript, the event loop enables the non-blocking, single-threaded nature of the language.

Happy Coding ✨