事件循环
Node的特点:
- 单线程
- 异步I/O
- 事件和回调
下面的问题:输出顺序分别是什么?
问题1
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
// 输出结果有两种可能
// timeout
// immediate
// 或
// immediate
// timeout
问题2
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
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操作的机制
事件循环每个周期就是一个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 的时间
- 处理轮询队列里面的事件
当进入轮询阶段,分两种情况:
- 如果有被调度的计时器,那么会先执行计时器,然后再执行需要调度的异步I/O
- 如果没有被调度的计时器,那么还会发生两种情况:
- 如果脚本被
setImmediate()
调度,那么会结束轮询阶段,进入check阶段,并执行被setImmediate()
调度的脚本。 - 如果脚本未被
setImmediate()
调度,则事件循环将等待回调被添加到队列中,然后立即执行。
- 如果脚本被
Timer > 异步I/O > Check
# Check阶段
允许在轮询阶段完成后立即执行回调。
即一旦结束当前轮询就会执行。
# Close阶段
如果套接字或处理函数突然关闭(例如 socket.destroy()
),则'close'
事件将在这个阶段发出。否则它将通过 process.nextTick()
发出。
- https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/
- https://developer.ibm.com/tutorials/learn-nodejs-the-event-loop/
编辑 (opens new window)
上次更新: 2021/11/10, 12:11:50