Being able to perform non-blocking I/O operations was one of the most vibrant selling points of Node.js back in the days. This article provides an introduction to the reactor pattern, the role of the libuv library and how they both empower Node.js to deal with multitasking pretty much as cool as Xabi Alonso does:
Thanks to this pattern we can use non-blocking I/O operations, which usually can take some time when being done in a classical, synchronous way (e.g. a database query, an HTTP request, reading a file).
When some part of code requests an I/O operation, the request is put in an Event Queue within an Event Demultiplexer. Then the control is immediatley returned back to the developer’s code. Thanks to this the application doesn’t freeze and can immediately continue with other operations (e.g. performing calculations, listening for UI events and responding to user’s input, etc.).
const fs = require('fs');
In the meantime, the so-called Event Loop processes the events stored in the Event Queue. It iterates through them and invokes the associated functions (callbacks or handlers) when ready.
Let’s see the example pseudocode implementation of an Event Loop taken from the book An Introduction to libuv by Nikhil Marathe:
while there are still events to process:
e = get the next event
if there is a callback associated with e:
call the callback
Each of the operating systems (Linux, Mac OSX, Windows) has its own implementation of an Event Demultiplexer. Therefore an abstraction layer above those implementations had to be introduced.
In the early days of Node.js a library called libev was used to handle abstraction above Mac OSX and Linux implementations of Event Demultiplexers (kqueue and (e)poll). As Node.js grew in popularity, this library became insufficient, because support for Windows was demanded as well.
Then the famous libuv was introduced, which covered it all (including Windows implementation: IOCP). It takes the responsibility for collecting events from the operating system or monitoring other sources of events. While being initially developed mostly with Node.js in mind, the libuv library is now widely used by different projects, including Mozilla’s Rust programming language.
I think that the best idea is to show it on a diagram. The architecture of a Node.js application can be presented as follows:
- Application code: self explanatory ;),
- V8: execution engine developed initially for Google Chrome,
- libuv: abstraction over Event Demultiplexer of a hosting OS.
I hope this short introduction is a good starting point of learning about the inner mechanisms that make Node.js environment work. We’ve only scratched the surface, so if you’re interested in more detailed information, I can suggest going through the references as the next step.
- An Introduction to libuv by Nikhil Marathe
- Node.js Design Patterns Second Edition by Mario Casciaro, Luciano Mammino
- The Node.js System by Aman Mittal