节点的事件循环阶段回调

Node's Event Loop Phases Callbacks

我在阅读节点的事件循环阶段,并说

  • 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', ...).

所以这里我有一个简单的代码来测试上面的一些阶段。当你执行代码时,你会得到这个输出:

  1. 关闭
  2. 立即
  3. 超时

但是根据文档,套接字回调是最后一个阶段。为什么先执行?

let socket = require("net").createServer();

socket.on("data", function (data) {
  console.log(data.toString());
});

socket.on("close", function (data) {
  console.log("close");
});

socket.listen(8080);

const fs = require("fs");

fs.readFile("readme.txt", () => {
  socket.close();
  setTimeout(() => {
    console.log("timeout");
  }, 0);

  setImmediate(() => {
    console.log("immediate");
  });
});

首先,请记住,由您在本地关闭套接字触发的 close 事件不是网络操作。它不是由传入的网络事件触发的。它由本地套接字实现触发,决定何时触发 close 事件以通知任何其他监视本地套接字的人它现在已关闭。它甚至可以同步触发(如果实现它的 net 库选择这样做)。因此,您所读到的有关网络事件在事件循环中如何确定优先级或排序的任何内容在这里均不适用。此事件不是由传入网络操作触发的,也不会像传入网络操作那样流经事件循环。

我在调试器中单步执行了您的代码并进入 socket.close() 以尝试查看实现的内容。它到达此行 here,其中调用 this._handle.close()。这进入了一些用 C++ 编写的 TCP 包装器层,调试器不会让您跟踪它以进一步查看。

该函数然后继续调用 emitCloseIfDrained(),后者调用 defaultTriggerAsyncIdScope() 并作为要使用的异步机制传递给 process.nextTick()

因此,似乎在本地调用 socket.close() 会导致套接字库在调用 close 事件之前使用 process.nextTick(),这将使其具有相当高的优先级,以便在其他事件之前得到处理事情,但会在事件循环的未来滴答声中处理。

但是,我希望您现在可以了解这是如何超级实现依赖的(取决于事件在触发它的任何库中是如何触发的)并且没有记录,因此不应在您的实现中依赖。出于求知欲的目的,想要尽可能多地了解这些内容是很好的,但是您不应该设计依赖于这种级别的实现细节的代码。一个特殊的功能,比如当 close 事件在套接字上触发时没有记录,并且没有保证它将来不会改变。如果您的代码需要异步事件的特定顺序,您需要编写代码来管理该顺序,以确保无论此级别的实现细节如何,它都会发生。