2

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?

8
  • Could you post the source of the statement? Commented Apr 27, 2020 at 11:18
  • "BUT this code...is not run in event loop" Who told you that? Commented Apr 27, 2020 at 11:18
  • @T.J.Crowder, hey T.J, I was just watching course on udemy. Is he wrong? Commented Apr 27, 2020 at 11:22
  • @T.J.Crowder, why did you delete your answer? Commented Apr 27, 2020 at 11:28
  • 1
    I think I've done that (now) in my corrected answer. Commented Apr 27, 2020 at 11:45

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.