node 下的 Event Loop

分享

前言

通过浏览器下的 Event Loop,可以得知 JavaScript 是一门事件驱动的语言,JavaScript 主线程通过不断调用事件队列中的事件来完成异步任务。那么,Node.js 下是否也是如此?

Node.js 下的事件队列

Node.js 官网有这样一篇文章:The Node.js Event Loop, Timers, and process.nextTick()
该文章主要讲述了 Node.js 中如何处理以及实现 Event Loop

主要包含以下内容:

事件队列

不同于浏览器,Node.js 下有 6 个事件处理阶段,其执行顺序和队列名称如下:

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

不同事件队列的含义如下:

阶段名 含义
timers 处理由 setTimeoutsetInterval 产生的回调
pending callbacks 处理由系统调用错误产出的回调,比如网络连接失败的情况
idle, prepare Node.js 内部使用
poll 处理由 I/O 产生的事件
check 处理由 setImmediate 产生的回调
close callbacks 处理由某些对象发出的 close 事件,比如 socket.on('close', ...)

timers 阶段

timers 阶段是 Node.jsEvent Loop 的第一个阶段。在该阶段,Node.js 会检查所有注册的定时器是否到期,如果到期,则压入 timers 的任务队列中。检查完毕后,依次执行任务队列中的任务。

注:Node.js 下设置定时器,不能保证在设定时间到达后立即执行。Node.js 只能保证在设定时间到达后,尽可能快地执行设置的回调。

pending callbacks 阶段

该阶段的任务队列主要由操作系统发出的任务失败的事件构成,比如网络连接失败。

poll 阶段

该阶段为 Node.js 中最重要的一个阶段。Node.js 下绝大多数异步任务都会在这个阶段进行处理,而不像其他阶段都是同种类型的任务。

在进入该阶段前,会进行一次定时器统计,找出最近一次需要触发的定时器,避免 Node.js 被阻塞在该阶段,而定时器得不到运行。

进入该阶段后,会发生以下事情:

  1. 计算出有多久的时间可以用来触发任务。
  2. 依次执行该事件队列中的事件。

Event Loop 进入 poll 阶段后,根据定时器的设置情况,会发生以下事情:

未设置定时器

  1. 如果该阶段的事件队列不为空,Node.js 将同步执行事件队列中的事件回调,直至该队列为空,或执行的回调达到系统上限。
  2. 如果该阶段的事件队列为空,那么系统将根据是否设置了 setImmediate 做出相应处理:
    • 设置了 setImmediate,那么该阶段结束,进入 check 阶段。
    • 未设置 setImmediate,那么 Node.js 将阻塞在该阶段,继续接收并处理产生的事件。

由于在 Node.js 下,该阶段接收了绝大多数事件,而且该阶段内的事件都不是固定产生的。相比定时器在固定时间触发,关闭事件由 close 事件触发(close 事件由程序主动触发),因此,该阶段应该是 Event Loop 中最重要的一个阶段。所以程序应该尽可能留在该阶段处理事件。当没有定时器,也没有设置 setImmediate 时,Node.js 继续进行 Event Loop 也没有意义,只需要停留在该阶段,继续处理事件即可。

设置了定时器

当该阶段的任务队列处理结束后,Node.js 会检查是否有定时器到期。如果有一个以上的定时器已到期,Event Loop 会结束该阶段,进入下一个阶段。

check 阶段

该阶段的任务队列中,保存着该阶段执行前所有由 setImmediate 注册的回调。

close callbacks 阶段

该阶段的任务队列主要由 close 发出的事件构成,比如 socket.on('close', ...)

micro task

同样,Node.js 下的 Event Loop 也有一个微任务队列。在 Node.js 下产生微任务的方式有:

  • process.nextTick()
  • Promise.then catch finally

不同于浏览器端,微任务队列会在 Event Loop 每个阶段执行结束后执行,每次都会将微任务队列中的所有任务执行完毕。

图解

node 微任务 & 宏任务

对比浏览器

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
}, 0)

以上代码在浏览器和 Node.js 下会得到不同的结果,图解如下:

浏览器:

timer1
promise1
timer2
promise2

Node.js

timer1
timer2
promise1
promise2

浏览器下的任务执行过程:

浏览器任务执行顺序

Node.js 下的任务执行过程:

Node 任务执行顺序

总结

  1. Node.js 下有 7 个任务队列,分别对应 6Event Loop 阶段和一个微任务队列。
  2. poll 阶段处理了绝大多数异步任务。如果其他阶段没有任务,Event Loop 会在这里停留,等待新的任务。
  3. Node.js 下微任务的执行时机在每个阶段结束后,而浏览器则是在每个宏任务结束后。

最后,根据前面的内容,得出 Node.jsEvent Loop 的执行过程如下:

Node 下 Event Loop
  • 橙色为 Event Loop 的主要内容。
  • 蓝色为同一个微任务队列。
  • 褐色为 poll 阶段内部的一个循环。
  • 黑色为进出 poll 阶段的逻辑判断。

参考

阅读更多

JavaScript 对象 & 原型

前言 这次的 why what or how 主题:JavaScript 对象 & 原型。 此类文章在百度上一搜一大把,其实不用再写了。但是本着把这个问题解释得清清楚楚、明明白白的想法,还是开始写了。 原因如下: 1. 面试宝典类文章,放张图片一糊弄,让人觉得自己理解了。 2. 解释类文章,告诉你一堆语法,对语法一顿解释,告诉你就是这样的,还是没说清楚。 3. 很少有文章单独解释这个点!但这个点是基础!真的很重要! 所以,本篇文章想说一说对象 & 原型。但为了确保能顺利理解,请先看完 JS 变量存储?栈 & 堆?NONONO!。因为该篇文章会从变量存储的角度解释 JavaScript 对象 & 原型,请确保看完,再看这篇文章。

By Breeze

浏览器下的 Event Loop

前言 JavaScript 以单线程的形式运行在宿主环境下,并采用回调的形式来解决异步任务。 为什么是单线程? JavaScript 最开始出现,是为了给 Web 页面增添一些动态效果,那么就避免不了获取页面上的元素信息。如果 JavaScript 以多线程的形式运行在浏览器内,两个线程内的 JavaScript 同时获取或修改某个页面上的元素,那么浏览器该让哪个 JavaScript 线程拥有获取或修改该元素的权限呢?由于元素信息会经常发生变化,那么,又该如何同步各个线程内所保存的元素信息呢? 所以,综合以上问题,JavaScript 是单线程的原因就显而易见了。单线程执行时,对于元素信息的引用在同一时间仅可能只有一个,那么以上所有问题都不存在了。 什么是异步任务? 任何代码在执行时,都会碰到一些需要大量时间运算或等待的代码。在浏览器环境下,常见的就是 HTTP 任务,比如资源加载(图片的 onload 事件)、Ajax 请求(XMLHttpRequest 的 onload 事件),还有页面元素的点击事件以及定时器等。 以上任务都极其耗时,而且会受环境影响。

By Breeze

什么是 HTML 5?

前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how,每篇文章试图解释清楚一个问题。 这次的 why what or how 主题:现在几乎所有人都知道了 HTML5,那么 H5 相比于 HTML4 到底有什么区别呢? 升级版?标准版! HTML5 作为 HTML 标准的第 5 版,于 2014 年发布。相信关注过 HTML5 发展史的朋友都知道,该版本是 WHATWG 和 W3C 握手言和后诞生的,是 W3C 组织与浏览器厂商相互妥协的结果。其中的绝大部分规范都由 WHATWG 组织制定,之后由 W3C

By Breeze

什么是 HTML?

前言 作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how,每篇文章解释清楚一个问题。 作为 why what or how 的第一章,先选一个较为简单的话题:什么是 HTML? 释义 HTML——HyperText Markup Language,超文本标记语言。 标记语言,一类以固定的形式描述文档结构或是数据处理细节的语言,一般为纯文本形式,其内容作为其他程序的输入。 因此,HTML 是一种用于描述网页的标记语言,纯文本,其内容作为浏览器的输入。浏览器会解析 HTML 文本内容,最终呈现页面。 那么超文本又是什么? 在网页出现前,人们的阅读习惯是从前往后、从上到下,就像阅读小说,必须从头到尾地看完。思维是单向的,并不存在岔路口。而超文本从字面上理解,就是超出了一般的阅读习惯:看文章时,思维是发散的,

By Breeze