输入关键词开始搜索

事件循环

617 字 约 2 分钟

Node的特点:

  • 单线程
  • 异步I/O
  • 事件和回调

下面的问题:输出顺序分别是什么?

问题1

js
setTimeout(() => {
  console.log('timeout');
}, 0);

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

// 输出结果有两种可能
// timeout
// immediate
// 或
// immediate
// timeout

问题2

js
const fs = require('fs');

Promise.resolve().then(() => {
  console.log('123');
})

fs.readdir('./test', () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(() => {
    console.log('456');
  })

  setImmediate(() => {
    console.log('immediate');
  });
});
// 输出顺序
// 123
// 456
// immediate
// timeout

问题3

js
const fs = require('fs');

(function mainline() {
  process.nextTick(() => {
    console.log('A');
  });
  console.log('B');
  setTimeout(() => {
    console.log('C');
  }, 2000);
  setImmediate(() => {
    console.log('D');
  });
  setTimeout(() => {
    console.log('E');
  }, 0);
  fs.readdir('./test', 'utf8', (err, files) => {
    console.log('F');
    setTimeout(() => {
      console.log('G');
    }, 0);
    setImmediate(() => {
      console.log('H');
    });
    process.nextTick(() => {
      console.log('I');
    });
  });

  console.log('J');
})();

// B J A E D F I H G C
// 或
// B J A E F I D H G C

执行顺序为什么会是这样的结果,这和事件循环有关。

事件循环 ​

启动Node,我们所编写的代码执行位置:

  • 主线
  • 事件循环

什么是事件循环,事件循环其实是Node.js处理非阻塞I/O操作的机制

Node 应用程序生命周期

事件循环每个周期就是一个Tick,每个阶段有自己的FIFO队列

非阻塞I/O:

  • 异步I/O:Node提供的内部异步API;
  • 非异步I/O:
    • setTimeout()
    • setInterval()
    • setImmediate()
    • process.nextTick()

阶段概述 ​

Timer阶段:执行setTimeout() 和 setInterval()调度的回调函数

Pending阶段:延迟到下一个循环迭代的I/O操作;如tcp报错,等待错误

idle, prepare阶段:仅系统内部使用

Poll(轮询)阶段:检索新I/O;执行与I/O相关回调

check阶段:执行setImmediate()调度的回调函数

close阶段:一些关闭的回调函数,如socket.on('close', ...)

Timers阶段 ​

定时器会的回调会尽可能早地被调用。

轮询阶段控制定时器何时执行。

Pending阶段 ​

延迟到下一个循环迭代的I/O操作。

此阶段对某些系统操作(如 TCP 错误类型)执行回调。例如,如果 TCP 套接字在尝试连接时接收到 ECONNREFUSED,则某些 *nix 的系统希望等待报告错误。这将被排队以在 挂起的回调 阶段执行。

Idle、Prepare阶段 ​

仅系统内部使用

Poll阶段 ​

重要功能:

  • 计算应该阻塞和轮询 I/O 的时间
  • 处理轮询队列里面的事件

当进入轮询阶段,分两种情况:

  1. 如果有被调度的计时器,那么会先执行计时器,然后再执行需要调度的异步I/O
  2. 如果没有被调度的计时器,那么还会发生两种情况:
    1. 如果脚本被 setImmediate() 调度,那么会结束轮询阶段,进入check阶段,并执行被setImmediate()调度的脚本。
    2. 如果脚本未被setImmediate()调度,则事件循环将等待回调被添加到队列中,然后立即执行。

Timer > 异步I/O > Check

Check阶段 ​

允许在轮询阶段完成后立即执行回调。

即一旦结束当前轮询就会执行。

Close阶段 ​

如果套接字或处理函数突然关闭(例如 socket.destroy()),则'close' 事件将在这个阶段发出。否则它将通过 process.nextTick() 发出。

  1. https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/
  2. https://developer.ibm.com/tutorials/learn-nodejs-the-event-loop/