SAM
SAM

Reputation: 1121

Confusion with what exact code is called by event loop in Node.js

I was learning Node.js and was watching video on what code is executed by event loop what not. So, here is the code snippet:

const fs = require('fs')
setTimeout(()=>console.log("Timer 1 finished"), 0);
setImmediate(()=>console.log("Immediate 1 finished"));

fs.readFile("test-file.txt", ()=>{
console.log("I/O finished");
console.log("----------");

setTimeout(()=>console.log("Timer 2 finished"), 0);
setTimeout(()=>console.log("Timer 3 finished"), 3000);
})

console.log("Hello from the top-level code")

The question is why setTimeout and setImmediate which are inside the callback of readFile are called in event loop BUT this code:

setTimeout(()=>console.log("Timer 1 finished"), 0);
    setImmediate(()=>console.log("Immediate 1 finished"));

is not run in event loop. This statement was made by author of Node.js course but he did not explain why. Is he wrong?

Upvotes: 2

Views: 172

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1073968

BUT this code...is not run in event loop

That's literally true, but it's almost a matter of semantics, and not really all that useful a differentiation to make. You can think of it as the top-level code is run on the first pass of the loop as though it were an instantly-fired timer callback. But the documentation does say it's before, rather than during, the event loop:

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

(my emphasis)

But again, that's a distinction that really makes very little difference.

Note that the code run that way is more than just the lines you quoted in that part of the question; more on that in a moment.

Mostly, you can think of all JavaScript code as running within an event loop, but Node.js makes the distinction above and also has process.nextTick, which the documentation says:

...is not technically part of the event loop. Instead, the nextTickQueue will be processed after the current operation is completed, regardless of the current phase of the event loop. Here, an operation is defined as a transition from the underlying C/C++ handler, and handling the JavaScript that needs to be executed.

That's a very Node.js-specific thing. And note that while they say it's technically outside the event loop, it still runs during the event loop, it's just that nextTick doesn't add a task to that main loop, it adds it to a different queue that is processed as soon as possible, regardless of what phase the event loop is in.

It's well worth reading that entire page for a better understanding.

About what code in your example is run when, the documentation linked above has this handy chart of the Node.js event loop:

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

...and gives this high-level overview of those phases:

  • timers: this phase executes callbacks scheduled by setTimeout() and setInterval().
  • pending callbacks: executes I/O callbacks deferred to the next loop iteration.
  • idle, prepare: only used internally.
  • poll: retrieve new I/O events; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate()); node will block here when appropriate.
  • check: setImmediate() callbacks are invoked here.
  • close callbacks: some close callbacks, e.g. socket.on('close', ...).

Based on that, here's how I think the code in your question gets run:

const fs = require('fs')                     // A
setTimeout(()=>                              // A
    console.log("Timer 1 finished")          //   B
, 0);
setImmediate(()=>                            // A
    console.log("Immediate 1 finished")      //     C
);

fs.readFile("test-file.txt", ()=>{           // A
    console.log("I/O finished");             //       D
    console.log("----------");               //       D
                                             //       D
    setTimeout(()=>                          //       D
        console.log("Timer 2 finished")      //   B
    , 0);
    setTimeout(()=>                          //       D
        console.log("Timer 3 finished")      //   B
    , 3000);                                 // A
})

console.log("Hello from the top-level code") // A
  • A: During initial evaluation, "before" the event loop
  • B: During the "timers" phase of the event loop
  • C: During the "check" phase of the event loop
  • D: During the "poll" phase (or possibly the "pending callbacks" phase of readFile defers the callback, I don't know which

Upvotes: 1

Related Questions