Node 事件循环
事件循环
相信对计算机有了解的伙伴都知道,Java、C++、Python 等编程语言都支持多线程,当有多个任务需要并发执行时,可以通过开启新的线程来同时执行多项任务,但是 JS 是单线程的,它无法并行处理多个任务,因此 JavaScript 提出
异步
的概念,本质上是将任务按轻重缓急来安排执行顺序,这就是事件循环机制
,使得各个任务之间的执行有条不紊。
- HTML5 新增
WebWorker
,可以实现多线程,但子线程依然无法操作 DOM ,其实 JS 设计为单线程很大程度上就是因为多线程并发渲染 DOM 会出现矛盾,导致页面显示出问题,所以即使开启了多线程功能,子线程也无法操作 DOM,避免了渲染出错。
Node 的事件循环机制
执行流程
- 在 Node 当中,一段脚本总是自上而下执行,同步代码立即执行;
- 异步代码进入异步模块以非阻塞的方式执行,对应的异步回调函数会在异步代码执行完毕之后被派发到不同的任务队列当中。
- 同步代码执行完毕之后,会先执行
nextTick 队列
里的任务,再执行微任务队列
,然后进入事件循环里的队列
。事件循环里有3个队列,Timer队列,Poll队列,Check队列
。
- Timer 队列用于处理定时器的回调,如 setTimeOut,setInterval ;
- Poll 队列用于处理 I/O 操作的回调,如文件读写,数据库操作,网络请求;
- Check队列,用于处理 setImmediate 。
事件循环里的3个队列按照从上往下的顺序周而复始地执行,如果 Timer 和 Check 队列有任务,循环会一直进行,如果 Timer 和 Check 队列没有任务,会在 Poll 队列处暂停,以等待 I/O 操作,因为默认情况下,最希望处理网络请求,尽快地给客户端响应。
特别案例
特别地,讲一下 setTimeOut 和 setImmediate
setTimeout(() => {
console.log("setTimeOut");
}, 0);
setImmediate(() => {
console.log("setImmediate");
});
- 按照上面的说法,从上往下执行脚本时,遇到 setTimeout 会放进 Timer 队列中,遇到 setImmediate 会放进 Check 队列中,而宏任务的事件循环中,Timer 队列里的回调先执行,那么是 setTimeOut 先执行吗?
- 结果打印出来,同一段代码居然有两种输出结果,第一次运行 node test.js 时,setImmediate 先执行,第二次运行 node test.js ,又是 setTimeOut 先执行,是什么原因呢?
因为在 Node 当中,setTimeOut 的时间参数最小是 1ms,即使写了0,也会是 1ms 之后才将任务放到 宏任务 Timer 队列当中,如果系统运行得足够快,可能 Node 还没来得及将 setTimeOut 放到 Timer 队列中,而已经将 setImmediate 放入 Check 队列当中,当系统运行足够快时,事件循环机制一旦启动,就会把此时已经进 Check 队列的任务先执行,而后再去执行 Timer 队列中的 setTimeOut,所以此情况下,这两者的执行顺序是受系统运行速度影响的,不具有确定性。
- 如果我们想要让 setImmediate 被优先调用,可以
将 setTimeOut 和 setImmediate 放入一个 I/O 操作中
,因为 I/O 操作执行时是处于 Poll 队列,下一步就要到 Check 队列了,就会把 setImmediate 里面的回调函数先执行,然后事件循环到 Timer 队列,就会执行 setTimeOut 里面的回调函数。
转载自:https://juejin.cn/post/7228846969472745528