Understanding Node.js Event Loop

Posted April 14, 2016 - 6 min read
Topics:  

JavaScript is a single thread language which allows executing asynchronous and non-blocking tasks.

The concurrency model of the JavaScript is based on a mechanism which is known as the event loop.

Having a clear understanding about the event loop and knowing how it works is a must-have for any JavaScript developer as it helps to write efficient code without falling in the pitfalls of the asynchronous programming in JavaScript.

Within this post we are going to discuss what is the event loop and how the concurrency model of the JavaScript is achieved.

High-level Overview


     ┌──────────────────────────┐      ┌─────────────────────────┐
     │        JavaScript        │      │         Node.js         │
     │                          │      │                         │
     │    Heap      Call stack  │      │  ┌────┐   ┌───────────┐ │
     │ ┌───────┐   ┌──────────┐ │      │  │HTTP│   │File system│ │
     │ │       │   │          │ │      │  └────┘   └───────────┘ │
     │ │    ┌┐ │   │          │ │      │                         │
     │ │    └┘ │   │          │ │      │      ┌──────┐           │
     │ │ ┌┐    │   │          │ │      │      │Timers│           │
     │ │ └┘    │   │          │ │      │      └──────┘           │
     │ │       │   ├──────────┤ │      │                         │
     │ │  ┌┐   │   │          │ │      │ ┌──────┐                │
     │ │  └┘   │   ├──────────┤ │      │ │Crypto│                │
     │ │       │   │          │ │      │ └──────┘                │
     │ │       │   ├──────────┤ │      │                         │
     │ └───────┘   └────▲─────┘ │      │                         │
     └──────────────────┼───────┘      └─────────┬───────────────┘
                        │                        │
             ┌──────────┴───┐                    │
             │  Event Loop  │                    │
             │              │                    │
             └──▲───────────┘                    │
                │                                │
           ┌────┴────────────────────────────────▼────┐
           │                Event Queue               │
           │                                          │
           │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐            │
           │ │   │ │   │ │   │ │   │ │   │            │
           │ └───┘ └───┘ └───┘ └───┘ └───┘            │
           └──────────────────────────────────────────┘

As shown in the diagram above, the event loop is not a part of the JavaScript language itself.

The event loop and different APIs are provided by the application environment.

It can be the Node.js environment or the web browser.

JavaScript Language

Call Stack

The single threaded nature of the JavaScript means that it can do only one thing at a time using a single call stack.

The call stack as in every programing language is a simple LIFO (Last In, First Out) queue that keeps track of the execution order of the functions that a script calls.

Each time you call a function, it is placed on the top of call stack. When the function finishes its work and exists, it is removed from the stack.

When a function calls another function, the new function is added on the top of the stack.

Heap Memory

JavaScript stores application data in two different places: heap and stack.

In a few words, the stack memory is used for storing static data which size can be predictable at the compile time. Stack memory can hold primitive values and references to functions and objects.

In contrast, the heap memory is used for storing functions and objects which size can be known only at run time and the memory space is allocated dynamically as needed.

Node.js

The Node.js environment provides many APIs and features which can be used by your application.

These APIs are highly optimized and implemented using a low-level code (C/C++). They are executed by the application environment (Node.js/Browser) and from outside the call stack.

Event Queue

For simplifying the overview only one abstracted queue has been shown in the diagram from above: The event queue.

In fact, the event loop has many event queues for different types of events that we will discuss in the event loop section.

An event queue, which is a FIFO (First In, First out) queue, is the place where the callbacks of asynchronous tasks are enqueued in order to be dequeued, by the event loop, and moved to the call stack for processing.

In other words, when the JavaScript engine executes the statements of our application code and meets an asynchronous function, for example setTimeout, this function is passed in to and handled by the JavaScript environment (Node.js/Browser) while the JavaScript engine is executing the next statements.

When an asynchronous function is ready, its callback is enqueued into its corresponding event queue. For example callbacks of setTimeout get enqueued in the timers queue.

Event Loop

The event loop is an infinite loop during which callback functions are moved from different event queues to the Call stack for execution.

While the call stack is executing some code, the event loop is blocked and waits until the call stack is empty.

That is why it is recommended not to block the call stack by executing tasks that may require a lot of computations which may cause your application to become very slow.

Event Queues

The event loop processes 4 types of event queues:

  • Timers events queue - Callbacks of expired setTimeout/setInterval timers
  • IO events queue - Callbacks of IO tasks
  • Immediate events queue - Callbacks of setImmediate()
  • Close handlers events queue - Callbacks (handlers) of any ‘close’ event.

The processing of each event queue is called an event loop phase. When an event loop phase finishes, before moving to the next phase, the event loop checks two additional queues which are called intermediate queues for available events:

  • process.nextTick events queue - Callbacks of process.nextTick()
  • Promise events queue - Callbacks of resolved/rejected promises.

The following diagram demonstrates the lifecycle of an event loop iteration:


      ┌─────────────────────────────┐
  ┌───►    Timers events queue      │
  │   │                             │
  │   └───────────────┬─────────────┘
  │                   │
  │                   ├────────────────┐
  │                   │                │    ┌───────────────────────────────────┐
  │   ┌───────────────▼─────────────┐  │    │                                   │
  │   │     IO events queue         │  └────► ┌───────────────────────────────┐ │
  │   │                             │       │ │ process.nextTick events queue │ │
  │   └───────────────┬─────────────┘       │ │                               │ │
  │                   │                     │ └─────────────┬─────────────────┘ │
  │                   ├─────────────────────►               │                   │
  │                   │                     │ ┌─────────────▼─────────────────┐ │
  │   ┌───────────────▼─────────────┐  ┌────► │     Promise events queue      │ │
  │   │   Immediate events queue    │  │    │ │                               │ │
  │   │                             │  │ ┌──► └───────────────────────────────┘ │
  │   └───────────────┬─────────────┘  │ │  │                                   │
  │                   │                │ │  └───────────────────────────────────┘
  │                   ├────────────────┘ │
  │                   │                  │
  │   ┌───────────────▼─────────────┐    │
  │   │ close handlers events queue │    │
  │   │                             │    │
  │   └───────────────┬─────────────┘    │
  │                   │                  │
  │                   ├──────────────────┘
  │                   │
  └───────────────────┘

Conclusion

JavaScript is a powerful event-based language which differs from other programing languages that use a classic request/response model.

As being asynchronous and with the help of the event loop the JavaScript language allows executing concurrently many tasks.

Nevertheless, as for any technology, JavaScript is not perfect.

One of its drawbacks is that it is not suited for tasks that use a high level of computation.