Node.js 事件循环终极面试题

155 阅读2分钟

废话不多说,直接上代码:

function block(ms) {
  let start = Date.now();
  while (Date.now() - start < ms) {}
}
setTimeout(() => {
  console.log(1);
  setImmediate(() => {
    console.log(2);
  });
  new Promise((resolve) => {
    console.log(3);
    resolve();
  }).then(() => {
    console.log(4);
    process.nextTick(() => {
      console.log(5);
    });
  });
  process.nextTick(() => {
    console.log(6);
    Promise.resolve().then(() => {
      console.log(7);
    });
  });
});
setImmediate(() => {
  console.log(8);
});
setTimeout(() => {
  console.log(9);
  process.nextTick(() => {
    console.log(10);
  });
});
block(10);

解析

Node Event Loop 的 6 个阶段

  • 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', ...). 摘自 Node.js 官网文章

过程太多不好理解,按我自己的理解总结:

  1. Timer Phase:同步代码执行完毕,进入 Event Loop,首先会检查是否有到期的 Timer,执行完所有 timer callback
  2. I/O Phase:执行所有的 I/O callback
  3. Polling:如果 Immediates 队列没有任务,就一直等待直到有新的 I/O 完成,或者到达系统设定的最大等待时间;如果有 Immediate 任务就直接进入下一阶段
  4. Immediate Phase:执行所有 Immediate callback
  5. close callback:执行所有 close callback

配合下图理解更佳:

nextTick 队列和 Promise 队列

  • 两个队列都是在每完成 一个宏任务 之后执行(Node v11 之后)
  • nextTick 队列 优先 于 Promise 队列
  • 必须 清空 一个队列的所有任务才能转向另一个队列

小练习:

Promise.resolve().then(() => {
  console.log(1);
  process.nextTick(() => {
    console.log(2);
  });
  Promise.resolve().then(() => {
    console.log(3);
    Promise.resolve().then(() => {
      console.log(7);
    });
  });
});
process.nextTick(() => {
  console.log(4);
  process.nextTick(() => {
    console.log(5);
  });
  Promise.resolve().then(() => {
    console.log(6);
  });
});

答案:4 5 1 6 3 7 2

Promise 原理

你只需要知道在 new Promise(executor) 里的 executor 是同步的,而 .then(callback) 是异步的即可。这部分不了解的自行搜索 “Promise原理”

附带我的 Promise 实现代码:MarvinXu/understanding-promise: An easy-to-understand version of Promise/A+ implementation

setTimeout 和 setImmediate

下面的代码输出结果并不确定,因为 Node.js 中的 timer 延时最小为 1ms,有可能在 1ms 后事件循环已经过了 timer phase,而先输出 immediate,反之亦然

setTimeout(() => {
  console.log("timeout");
}, 0);
setImmediate(() => {
  console.log("immediate");
});
// 先后不确定

所以我在开始的题目中加了 block(10) ,是为了保证进入事件循环时 timer 已经到期

我的解题方法

脑子里按行去执行代码,把对应 callback 加入队列,然后依次去清空每个队列,即得到最终结果

timeout: 1 9
immediate: 8
nextTick:
promise:

output: 

答案

1 3 6 4 7 5 9 10 8 2