A Word About Node.js Event Loop

DZone 's Guide to

A Word About Node.js Event Loop

This developer wanted further clarification on how an event loop works. Check out what his research resulted in for a better understanding.

· Web Dev Zone ·
Free Resource

Up until recently, I was pretty sure how Node's Event Loop worked; in fact, I was sure I knew how asynchronous calls were handled.

Recently, thanks to a very interesting talk about this subject by Daniel Khan in last year's NodeConf, my eyes were opened to the truth. He has a very good explanation in that video, but I decided to go deep into Node's official documentation about it, and out of that came my own explanation of how this beast known as Event Loop works.

Spoiler alert: it's very similar to Daniel's, but in my case, the results from his tests were a bit different, so I added an extra explanation for it.

Essentially, the Event Loop works on phases; for every tick of the loop, it goes through a new phase. For every phase, it keeps a FIFO queue of callbacks, and it executes as many callbacks in that phase as it can (there is a limit to how many callbacks can be called on every tick).

There is a common misconception that the event loops handles all asynchronous functions with threads from Libuv (the library V8 uses to handle asynchronism). But it’s not exactly true; these threads are only used in special cases when there is no other way because modern OS and other systems, such as databases, already provide asynchronous interfaces, so the engine will try to use them and if there is no async alternative, it’ll resort to the thread pool.

The aforementioned phases are:

    • Timers: Here is where setTimeout and setInterval and are evaluated.
    • Callbacks: Here is where system-specific callbacks are executed.
    • Poll: This is where I/O related callbacks get executed, as long as there are any. Once all callbacks have been executed (or the system’s limit is reached) if there is nothing else to execute, then the engine will wait for new callbacks to be registered in this phase to execute them.
    • Check: Here is where setImmediate  is evaluated.
    • Close events: Here is where the callbacks registered for the ‘close’ events are executed.

If you wanted to, there is a very interesting way to look into this, and test it on your own. Simply by using the following code, you’ll see how the execution is done internally:

const request = require("request"),
      fibonacci = require("fibonacci"),
      fs = require("fs");

process.nextTick(() => {
    process.stdout.write("NT #1\n");

fs.readFile("./index.js", (err, data) => {
    process.stdout.write("1: I/O Polling...\n");

request.get("http://google.com", (err, res, body) => {
    process.stdout.write("2: System polling...\n")

setImmediate(() => {
    process.stdout.write("3: Set Immediate phase...\n");

setTimeout(() => {
    process.stdout.write("4: Timers...\n");
}, 0);

process.stdout.write("5: Fibonacci(20): " + fibonacci.iterate(20).number + " - Callback\n");

process.nextTick(() => {
    process.stdout.write("NT #2\n");

Example showing the different phases for the event loop

The result from executing the above code is the following (note that this is in my computer; with Node version 8.1.3, results might vary on your side, but they should be similar nonetheless):

    • 5: Fibonacci(20): 6765 callback
    • NT #1
    • NT #2
    • 4: Timers...
    • 3: Set Immediate phase...
    • 1: I/O Polling...
    • 2: System polling…

From these results, we can extrapolate the following behavior:

    1. The first thing to get executed (and resolved) is the current thread, which means the line of the Fibonacci call. The rest of the lines were executed too, but they have not resolved yet, because they’re all different forms of asynchronous behavior.
    2. After the current phase is over, the event loop tries to move to the next one, but first it needs to call the nextTick method, which is executed on a separate queue. This means that no matter what the current phase is, it’ll resolve all callbacks you’ve defined and then move to the next phase.
    3. By this time, the Poll phase has probably run, and it has verified that no callback has been registered for it yet (because the file is still being read and the HTTP Request is still being made, and the callbacks are only added to the phase's queue once they need to be executed), so it continues to the Timers and Check phases.
    4. In this case, depending on the performance of your system, either one of them can run. The point here is that since we’ve setup the timeout for setTimeout to 0, the execution of that callback and the one for setImmediate is non-deterministic*. In other words, you won’t be able to predict their order of execution, unless you do it inside an I/O callback (again, check the docs, it's very interesting)
    5. Finally, after the timers have run, the only thing missing is to wait on the Poll phase until the I/O operations end, and given the nature of them, reading the file will end first, leaving the HTTP request to be resolved in the last place.

The following diagram attempts to present a graphical representation of the above events, starting from what would be step #3.

Event loop phases

Diagram showing the transition between phases in the above code example

You can read more on the how the event loop works from the official documentation, but this should give you a basic idea of how it should work. You can tweak and change the code sample to see how the results change until you’re sure there is no witchcraft involved (I know it took me several attempts to get there).

Let me know if this cleared anything up for you or not. This is just an extract of one of the chapters of my upcoming book, REST API Design with Node.js, which should be coming out shortly, so if you're interested in reading more about Node.js, its event loop and REST APIs, visit my site at http://www.fernandodoglio.com.

* See Node.js’ official documentation about this: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout

node.js ,event loop ,web dev ,event loop phases ,asynchronous calls

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}