简体   繁体   中英

Node.js Event-Loop Mechanism

I'm learning the mechanism of Event-Loop in Node.js, and I'm doing some exercises, but have some confusions as explained bellow.

const fs = require("fs");

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

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

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

console.log("Hello");

I expected to see the following output:

Hello
Timer 1
Immediate 1
I/O
Timer 2
Immediate 2
Timer 3

but I get the following output:

Hello
Timer 1
Immediate 1
I/O
Immediate 2
Timer 2
Timer 3

Would you please clarify for me how are these lines executed step by step.

The reason for this output is the asynchronous nature of javascript.

  • You set the first 2 outputs in a sort of timeout with the execution time to be 0 this makes them still wait a tick.
  • Next you have the file read which takes a while to be finished and thus delays the execution of the functions in the callback
  • The first console.log within the callback is fired as soon as the callback is executed and the rest within the callback follows the first part of your code
  • Lastly you have the console.log at the bottom which gets executed at first because there is no delay for it and it does not need to wait till the next tick.

As some added help, check out this video.

https://youtu.be/cCOL7MC4Pl0

The presenter gives an amazing talk on the event loop. I think it is a great resource.

While this is particularly for the browser, many aspects are shared in Node.

First off, I should mention that if you really want asynchronous operation A to be processed in a specific order with relation to asynchronous operation B, you should probably write your code such that it guarantees that without relying on the details of exactly what gets to run first. But, that said, I have run into issues where one type of asynchronous operation can "hog" the event loop and starve other types of events and it can be useful to understand what's really going on inside if/when that happens.

Broken down to its core, your question is really about why Immediate2 logs before Timer2 when scheduled from within an I/O callback, but not when called from top level code? Thus it is inconsistent.

This has to do with where the event loop is in its cycle through various checks it is doing when the setTimeout() and setImmediate() are called (when they are scheduled). It is somewhat explained here: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout .

If you look at this somewhat simplified diagram of the event loop (from the above article):

在此处输入图像描述

You can see that there are a number of different parts to the event loop cycle. setTimeout() is served by the "timers" block at the top of the diagram. setImmediate() is served in the "check" block near the bottom of the diagram. File I/O is served in the "poll" block in the middle.

So, if you schedule both a setImmediate(fn1) and a setTimeout(fn2, 0) from within a file I/O callback (which is your case for Intermediate2 and Timer2), then the event loop processing happens to be in the poll phase when these two are scheduled. So, the next phase of the event loop is the "check" phase and the setImmediate(fn1) gets processed. Then, after the "check" phase and the "close callbacks" phase, then it cycles back around to the "timers" phase and you get the setTimeout(fn2,0) .

If, on the other hand, you call those same two setImmediate() and setTimeout() from code that runs from a different phase of the event loop, then the timer might get processed first before the setImmediate() - it will depend upon exactly where that code was executed from in the event loop cycle.

This structure of the event loop is why some people describe setImmediate() as "it runs right after I/O" because it's positioned in the loop to be processed right after the "poll" phase. If you are in the middle of processing some file I/O in an I/O callback and you want something to run as soon as the stack unwinds, you can use setImmediate() to accomplish that. It will always run after the current I/O callback finishes, but before timers.

Note: Missing from this simplified description is promises which have their own special treatment. Promises are considered microtasks and they have their own queue. They get to run a lot more often. Starting with node v11, they get to run in every phase of the event loop. So, if you have three pending timers that are ready to run and you get to the timer phase of the event loop and call the callback for the first pending timer and in that timer callback, you resolve a promise, then as soon as that timer callback returns back to the system, then it will serve that resolved promise. So, microtasks (such as promises and process.nextTick() ) get served (if waiting to run) between every operation in the event loop, not just between phases of the event loop, but even between pending events in the same phase. You can read more about these specifics and the changes in node v11 here: New Changes to the Timers and Microtasks in Node v11.0.0 and above .

I believe this was done to improve the performance of promise-related code as promises became more of a central part of the nodejs architecture for asynchronous operations and there is also some standards-related work in this area too to make this consistent across different JS envrionments.

Here's another reference that covers part of this:

Nodejs Event Loop - interaction with top-level code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM