Nodejs 事件循环
详细介绍 Nodejs 中的 事件循环机制
事件循环经典题目
1 | async function async1() { |
该题目涉及 Nodejs 中所有事件循环相关概念, 弄清楚这个顺序则对于 Nodejs 的事件循环机制就有一个清晰的了解了, 这段代码的输出结果为:
1 | script start |
异步任务
异步任务有两种:
- 追加在本轮循环的异步任务
- 追加在次轮循环的异步任务
本轮循环一定早于次轮循环执行
Nodejs 规定 process.nextTick 和 promise 的回调函数追加在本轮循环, 同步任务执行完毕后就会开始执行他们, 而 setTimeout, setInterval 和 setImmediate 等 timers 的回调函数则追加到次轮循环
process.nextTick()
Nodejs 执行完所有同步任务, 接下来就会执行 process.nextTick 的任务队列, 属于本轮循环
微任务(microtask)
promise 的回调函数会进入异步任务里的微任务队列
微任务队列追加在 process.nextTick 之后, 也属于本轮循环
1 | Promise.resolve().then(() => console.log(1)); |
以上代码始终先输出 2 再输出 1
1 | process.nextTick(() => console.log(1)); |
输出结果 1, 3, 2, 4
开启 process.nextTick 任务会直接进入 nextTickQueue 中, 开启 promise 回调任务会直接进入 microTaskQueue 中, 只有前一个队列清空后才会执行下一个队列
async 和 promise
async 函数返回一个 promise 对象, 当函数执行时是作为一个同步函数执行, 直到遇到 await, 就会先返回, 等到执行微任务阶段完成这个异步任务, 再执行函数体内后续操作
而在 promise 本身函数体中的代码在 resolve 之后依然可以执行, 因为在 then 回调之前, 该函数体内还是属于同步任务阶段, 正规的写法应该不要在 resolve 或 reject 后执行任何操作
事件循环阶段
- timers 阶段
- 此阶段包括 setTimeout 和 setInterval
- IO callbacks
- 大部分回调事件, 普通的 callback
- poll 阶段
- 网络连接, 读取文件等操作
- check 阶段
- setImmediate
- close 阶段
- 一些 close 回调, 如 socket.on(‘close’, …) 等
开启事件循环
Nodejs 开始执行脚本时, 会先进行事件循环初始化, 此时还没有开始事件循环, 会优先处理以下任务:
- 同步任务
- 发出异步请求
- 规划定时器生效时间
- 执行 process.nextTick 回调
- 开始事件循环
setTimeout 和 setImmediate
由于 setTimeout 属于 timers 阶段, setImmediate 属于 check 阶段, 所以 setTimeout 始终早于 setImmediate 执行
1 | setTimeout(() => console.log(1)); |
理论上, 这段代码会先输出 1 再输出 2, 但有时也会先输出 2 再输出 1
因为 setTimeout 第二个参数缺省值为 0, 但 Nodejs 做不到 0 毫秒执行回调函数, 至少也要 1 毫秒, 所以 setTimeout(…, 0) 等于 setTimeout(…, 1)
基于系统当前状态, 进入事件循环时可能不到 1 毫秒也可能超过 1 毫秒, 如果不到 1 毫秒, 则会先进入 check 阶段, 就会先执行 setImmediate 回调函数
但是如果在 I/O callbacks 阶段执行上述代码, 则会先执行 check 再执行 timers
1 | const fs = require("fs"); |
上述代码必然先输出 2 再输出 1