下一个泡泡玛特工程师是谁呢?

收集一些自己用到的prompt

2023-04-05 · 1 min read

机械键盘相关参数

机械键盘相关参数记录

2023-04-05 · 1 min read

Web 前端性能优化

常用的web前端性能优化手段

2023-03-19 · 3 min read

Blender常用快捷键

自己经常用到的一些快捷键记录

2022-12-11 · 1 min read

Docker 常用命令

精简 Docker 常用命令

2022-11-06 · 1 min read

项目管理

项目管理相关知识

2022-11-06 · 1 min read

JS沙箱sandbox的各种实现

我们把Js隔离机制常常称作沙箱

2022-09-18 · 1 min read

Amazing!Solid 比react还react

今天来介绍2个amazing的东东

2022-09-12 · 1 min read

puppeteer应用

使用傀儡师来操作浏览器这个傀儡吧

2022-09-11 · 1 min read

做一个web termianl

前端react, 后端nodejs, 直接可用版web termianl

2022-07-17 · 1 min read

TypeScript里常用的工具类型

经常用到的工具类型,提取抽离出来,供以后复用

2022-06-26 · 1 min read

Vim大法好

想要丢掉鼠标,试试Vim

2022-06-26 · 1 min read

碧血丹心

无论时光如何沾染风霜,也永似红日光

2022-05-10 · 1 min read

chrome V8 引擎中的垃圾回收机制

V8引擎就是nodejs的发动机

2022-03-03 · 1 min read

如何开发一个cli

命令行交互界面是程序员必备的工具,如何开发一个呢?

2022-02-21 · 1 min read

Javascript中的哲学

道生一,一生二,二生三,三生万物

2022-02-20 · 1 min read

React Redux 实现 (Context 版)

React Redux 实现 (Context 版)

2022-02-16 · 1 min read

mobx-react 使用

虽然不常用,但是可以学一下

2022-02-16 · 1 min read

Less中的for和forEach循环

循环的使用是保持代码干燥和避免重复的好方法

2022-02-15 · 1 min read

Webpack Splitchunks 详解

webpack 优化

2022-02-15 · 1 min read

ssh-keygen命令详解

为ssh生成、管理和转换认证密钥

2022-02-15 · 1 min read

使用CURL发送POST请求

curl 是常用的命令行工具,用来请求 Web 服务器。

2022-02-15 · 1 min read

Webpack Plugin 开发

让我们来学一下如何开发一个webpack插件

2022-02-14 · 1 min read

JS 实现两个大数相加?

algo-adding-large-numbers

2022-02-10 · 1 min read

字典树 trie

字典树 trie

2022-02-09 · 1 min read

浏览器原理问题

浏览器原理问题

2022-01-11 · 1 min read

MacBook快速进入一个文件夹目录

mac如何快速进入一个文件夹

2021-10-26 · 1 min read

react合成事件

react-synthetic-event

2021-10-25 · 1 min read

mini webpack实现

通过babel核心来实现迷你版的webpack

2021-10-10 · 1 min read

设计模式

在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于类

2021-10-10 · 1 min read

babel核心

babel核心介绍

2021-10-07 · 1 min read

React 15 和 React 16 的区别

react-15-16

2021-10-06 · 1 min read

React性能优化

浅谈react性能优化的方法

2021-10-05 · 1 min read

交通信号灯实现

如何用js来实现交通信号灯呢

2021-09-25 · 1 min read

内存管理

前端中的内存管理

2021-09-25 · 1 min read

前端安全

前端关于安全方面的知识

2021-09-25 · 2 min read

网络和并发

http各版本对于并发的支持,前端如何控制并发量?

2021-09-25 · 1 min read

跨域方法

总结了9种跨域方法

2021-09-24 · 1 min read

react virtualList 虚拟列表无限滚动实现

用react实现虚拟滚动

2021-09-16 · 1 min read

监控埋点方案

前端监控埋点方案

2021-09-16 · 1 min read

Mini useEffect实现

如何实现useEffect?

2021-09-11 · 1 min read

Mini useState 实现

我们来思考一下useState是怎么实现的呢?

2021-09-11 · 1 min read

React Fiber

react-fiber

2021-09-11 · 1 min read

React class组件和function组件异同

类组件和函数组件有何相同点有何不同点呢?

2021-09-11 · 1 min read

Lodash Get 实现

algo-lodash-get

2021-09-08 · 1 min read

手写reduce实现

algo-reduce

2021-09-08 · 1 min read

Hooks 原理概览

react-hooks

2021-09-05 · 1 min read

Hook原理——状态Hook

react-hook-state

2021-09-05 · 1 min read

手写Mini Redux实现

手写一个简易版的redux实现,包含了核心逻辑

2021-09-04 · 1 min read

legacy和concurrent模式

react-legacy-concurrent

2021-09-01 · 1 min read

react架构

总体 react 的核心可以用 ui=fn(state)来表示 3 大核心对象、3 大核心阶段、2 大工作循环 Scheduler(调度器): 排序优先级,让优先级高的任务先进行 reconcile Reconciler…

2021-09-01 · 2 min read

react核心api和jsx

为什么要有jsx,为什么会有虚拟bom

2021-09-01 · 1 min read

setState是同步的还是异步的

react-setstate-usestate

2021-08-31 · 1 min read

如何使用NodeJs创建HTTP服务?

如何使用NodeJs创建HTTP服务?

2021-08-17 · 1 min read

NodeJS 事件循环模型

nodejs-eventloop

2021-08-11 · 2 min read

Buffer

nodejs中的内存管理

2021-08-10 · 1 min read

微前端解决方案-qiankun

目前国内最好的微前端解决方案-qiankun

2021-08-10 · 1 min read

React Mini版实现(1)

学一门技术最好的方法就是做一个其玩具版的实现,我们来尝试实现一下react和react-dom最简单版本吧

2021-08-04 · 1 min read

brew安装

brew 是 MacOS 上的包管理工具,可以简化 macOS 和 Linux 操作系统上软件的安装。

2021-08-04 · 1 min read

CommonJS简易版实现

CommonJS我们经常用,如何实现一个简易版的commonJS呢?

2021-08-01 · 1 min read

极品透明Dashboard样式分享

一个极品透明Dashboard样式分享

2021-07-30 · 1 min read

Stream

nodejs中的流

2021-07-11 · 1 min read

NodeJS全局对象

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

2021-05-18 · 1 min read

如何部署Nodejs服务

如何快速的部署一个Nodejs服务到公网呢?

2021-05-12 · 1 min read

Events

events模块是node的核心模块之一,几乎所有常用的node模块都继承了events模块,比如http、fs等。

2021-05-11 · 1 min read

用JS绘制背景,让CSS直接使用 'background:paint(xxx)'

CSS对象新特性,新的background设置方式

2021-04-30 · 1 min read

Block Formatting Context 块级格式化上下文

可以将BFC看成是元素的一种属性,拥有了这种属性的元素就会使他的子元素与世隔绝,不会影响到外部其他元素

2021-04-17 · 1 min read

如何使用Nodejs来创建一个TCP/UDP服务?

如何使用Nodejs来创建一个TCP/UDP服务?

2021-04-17 · 1 min read

Mac使用tree生成目录结构

程序员经常会有需求,需要列出项目的结构树。Mac或者Linux下可以使用tree列出项目结构

2021-04-04 · 1 min read

常见算法

一些比较常见算法

2021-03-20 · 1 min read

前端缓存

对于性能优化离不开缓存

2021-02-28 · 1 min read

前端答疑

一些比较常见的问题

2020-09-02 · 1 min read

Nodejs 网络 & HTTP

nodejs-network

2020-08-14 · 1 min read

JavaScript AST 抽象语法树

源代码的抽象语法结构的树状表现形式

2020-08-02 · 1 min read

nodejs里面向切面编程的一种范式

在一些场景下我们可能需要一种面向切面的编程方式

2020-08-01 · 1 min read

各种JS模块化特性

AMD、CMD、CJS、ESM

2020-05-30 · 1 min read

手写Ajax实现

使用HMR一步步实现Ajax

2020-05-30 · min read

手写PromiseA+实现

如何自己实现promiseA+规范,手写一个promise实现

2020-04-30 · 1 min read

debug和内存泄露

nodejs的debug方法

2020-04-08 · 1 min read

Javascript prototype 原型链

js-prototype

2019-09-07 · 1 min read

this指针、作用域

this是在执行时动态读取上下文决定的,不是在定义时决定

2019-06-14 · 1 min read

call、apply、bind的极简实现

使用symbol实

2019-06-03 · 1 min read

CSS联合选择器区分列表元素个数不同所要求的不同样式

对于列表,在有些时候针对于不同个数的item会有不同的显示,比如col份数,用js固然可以,是否可以用css更简便的实现呢?

2019-04-30 · 1 min read

TypeScript基础

介绍TypeScript基础知识

2019-04-30 · 1 min read

JS中的变量提升

为什么js当时要这样设计

2019-03-07 · 1 min read

ES6之Class

关于ES6里的class, 我们有什么不知道的事?

2019-03-01 · 1 min read

HTTP详解

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传输协议

2019-02-17 · 2 min read

极简代码实现节流Throttle和防抖Debounce

使用各9行代码实现节流和防抖函数

2019-01-30 · 1 min read

Javascript 事件循环 EventLoop

js-eventloop

2018-09-07 · 1 min read

POST和GET区别

POST和GET区别是什么呢?

2018-08-14 · 1 min read

正则表达式

正则表达式一锅端

2018-07-30 · 1 min read

cloneDeep 深克隆实现

algo-clonedeep

2017-09-07 · 1 min read

webpack

engin-webpack

2017-09-07 · 1 min read

http1.1和http2.0有什么区别

http1.1和http2.0有什么区别

2017-06-14 · 1 min read

手写EventEmitter事件巴士

咱们来手写实现一个EventEmitter事件巴士

2017-01-10 · 1 min read

OOP 面向对象编程

对象是什么?为什么要面向对象?

2016-05-08 · 1 min read
Stay hungry & Stay foolish
战歌
16
The Reluctant Warrior
Immediate Music
To Glory
Two Steps From Hell
Victory
Two Steps From Hell
Empire of Angels
Thomas Bergersen
Serenata Immortale
Immediate Music
Cornfield Chase
Hans Zimmer
Tennessee
Hans Zimmer
He's a Pirate
Martin Ermen
Rise
Hans Zimmer
On Thin Ice
Hans Zimmer
Angels Will Rise
Twisted Jukebo
When It All Falls Down
Audiomachine
Icarus
Ivan Torrent
Star Sky - Instrumental
Two Steps From Hell
亡灵序曲
L
Up Is Down
Hans Zimm
回到首页

NodeJS 事件循环模型

袁官东
August 11th, 2021 · 2 min read
图:Nguyen Nhut

什么是事件循环

事件循环使 Node.js 可以通过将操作转移到系统内核中来执行非阻塞 I/O 操作(尽管 JavaScript 是单线程的)。

由于大多数现代内核都是多线程的,因此它们可以处理在后台执行的多个操作。 当这些操作之一完成时,内核会告诉 Node.js,以便可以将适当的回调添加到轮询队列中以最终执行。

Node.js 启动时,它将初始化事件循环,处理提供的输入脚本,这些脚本可能会进行异步 API 调用,调度计时器或调用 process.nextTick, 然后开始处理事件循环。

1⌚️
2 ┌───────────────────────────┐
3┌─>│ timers │
4│ └─────────────┬─────────────┘
5│ ┌─────────────┴─────────────┐
6│ │ pending callbacks │
7│ └─────────────┬─────────────┘
8│ ┌─────────────┴─────────────┐
9│ │ idle, prepare │
10│ └─────────────┬─────────────┘ ┌───────────────┐
11│ ┌─────────────┴─────────────┐ │ incoming: │
12│ │ poll │<─────┤ connections, │
13│ └─────────────┬─────────────┘ │ data, etc. │
14│ ┌─────────────┴─────────────┐ └───────────────┘
15│ │ check │
16│ └─────────────┬─────────────┘
17│ ┌─────────────┴─────────────┐
18└──┤ close callbacks │
19 └───────────────────────────┘

每个阶段都有一个要执行的回调 FIFO 队列。 尽管每个阶段都有其自己的特殊方式,但是通常,当事件循环进入给定阶段时,它将执行该阶段特定的任何操作,然后在该阶段的队列中执行回调,直到队列耗尽或执行回调的最大数量为止。 当队列已为空或达到回调限制时,事件循环将移至下一个阶段,依此类推。

各阶段概览

  1. timers:此阶段执行由 setTimeout 和 setInterval 设置的回调。
  2. pending callbacks:执行推迟到下一个循环迭代的 I/O 回调。
  3. idle, prepare, :仅在内部使用。
  4. poll:取出新完成的 I/O 事件;执行与 I/O 相关的回调(除了关闭回调,计时器调度的回调和 setImmediate 之外,几乎所有这些回调) 适当时,node 将在此处阻塞。
  5. check:在这里调用 setImmediate 回调。
  6. close callbacks:一些关闭回调,例如 socket.on(‘close’, …)。

在每次事件循环运行之间,Node.js 会检查它是否正在等待任何异步 I/O 或 timers,如果没有,则将其干净地关闭。

各阶段详细解析

timers 计时器阶段

计时器可以在回调后面指定时间阈值,但这不是我们希望其执行的确切时间。 计时器回调将在经过指定的时间后尽早运行。 但是,操作系统调度或其他回调的运行可能会延迟它们。— 执行的实际时间不确定

1const fs = require("fs");
2
3function someAsyncOperation(callback) {
4 // Assume this takes 95ms to complete
5 fs.readFile("/path/to/file", callback);
6}
7
8const timeoutScheduled = Date.now();
9
10setTimeout(() => {
11 const delay = Date.now() - timeoutScheduled;
12
13 console.log(`${delay}ms have passed since I was scheduled`);
14}, 100);
15
16// do someAsyncOperation which takes 95 ms to complete
17someAsyncOperation(() => {
18 const startCallback = Date.now();
19
20 // do something that will take 10ms...
21 while (Date.now() - startCallback < 10) {
22 // do nothing
23 }
24});

当事件循环进入 poll 阶段时,它有一个空队列(fs.readFile 尚未完成),因此它将等待直到达到最快的计时器 timer 阈值为止。 等待 95 ms 过去时,fs.readFile 完成读取文件,并将需要 10ms 完成的其回调添加到轮询 (poll) 队列并执行。 回调完成后,队列中不再有回调,此时事件循环已达到最早计时器 (timer) 的阈值 (100ms),然后返回到计时器 (timer) 阶段以执行计时器的回调。 在此示例中,您将看到计划的计时器与执行的回调之间的总延迟为 105ms。

pending callbacks 阶段

此阶段执行某些系统操作的回调,例如 TCP 错误。 平时无需关注

轮询 poll 阶段

轮询阶段具有两个主要功能:

  1. 计算应该阻塞并 I/O 轮询的时间
  2. 处理轮询队列 (poll queue) 中的事件

当事件循环进入轮询 (poll) 阶段并且没有任何计时器调度 (timers scheduled) 时,将发生以下两种情况之一:

  1. 如果轮询队列 (poll queue) 不为空,则事件循环将遍历其回调队列,使其同步执行,直到队列用尽或达到与系统相关的硬限制为止 (到底是哪些硬限制?)。
  2. 如果轮询队列为空,则会发生以下两种情况之一: 2.1 如果已通过 setImmediate 调度了脚本,则事件循环将结束轮询 poll 阶段,并继续执行 check 阶段以执行那些调度的脚本。 2.2 如果脚本并没有 setImmediate 设置回调,则事件循环将等待 poll 队列中的回调,然后立即执行它们。

一旦轮询队列 (poll queue) 为空,事件循环将检查哪些计时器 timer 已经到时间。 如果一个或多个计时器 timer 准备就绪,则事件循环将返回到计时器阶段,以执行这些计时器的回调。

检查阶段 check

此阶段允许在轮询 poll 阶段完成后立即执行回调。 如果轮询 poll 阶段处于空闲,并且脚本已使用 setImmediate 进入 check 队列,则事件循环可能会进入 check 阶段,而不是在 poll 阶段等待。

setImmediate 实际上是一个特殊的计时器,它在事件循环的单独阶段运行。 它使用 libuv API,该 API 计划在轮询阶段完成后执行回调。

通常,在执行代码时,事件循环最终将到达轮询 poll 阶段,在该阶段它将等待传入的连接,请求等。但是,如果已使用 setImmediate 设置回调并且轮询阶段变为空闲,则它将将结束并进入 check 阶段,而不是等待轮询事件。

close callbacks 阶段

如果套接字或句柄突然关闭(例如 socket.destroy),则在此阶段将发出 ‘close’ 事件。 否则它将通过 process.nextTick 发出。

setImmediate 和 setTimeout 的区别

setImmediate 和 setTimeout 相似,但是根据调用时间的不同,它们的行为也不同。

  • setImmediate 设计为在当前轮询 poll 阶段完成后执行脚本。
  • setTimeout 计划在以毫秒为单位的最小阈值过去之后运行脚本。

Tips: 计时器的执行顺序将根据调用它们的上下文而有所不同。 如果两者都是主模块中调用的,则时序将受到进程性能的限制.

来看两个例子:

  1. 在主模块中执行

    两者的执行顺序是不固定的, 可能 timeout 在前, 也可能 immediate 在前

    1setTimeout(() => {
    2 console.log("timeout");
    3}, 0);
    4
    5setImmediate(() => {
    6 console.log("immediate");
    7});
  2. 在同一个 I/O 回调里执行

    setImmediate 总是先执行

    1const fs = require("fs");
    2
    3fs.readFile(__filename, () => {
    4 setTimeout(() => {
    5 console.log("timeout");
    6 }, 0);
    7 setImmediate(() => {
    8 console.log("immediate");
    9 });
    10});

问题:那为什么在外部 (比如主代码部分 mainline) 这两者的执行顺序不确定呢?

解答:在 主代码 部分执行 setTimeout 设置定时器 (此时还没有写入队列),与 setImmediate 写入 check 队列。

mainline 执行完开始事件循环,第一阶段是 timers,这时候 timers 队列可能为空,也可能有回调; 如果没有那么执行 check 队列的回调,下一轮循环在检查并执行 timers 队列的回调; 如果有就先执行 timers 的回调,再执行 check 阶段的回调。因此这是 timers 的不确定性导致的。

process.nextTick

process.nextTick 从技术上讲不是事件循环的一部分。 相反,无论事件循环的当前阶段如何,都将在当前操作完成之后处理 nextTickQueue

process.nextTick 和 setImmediate 的区别

  • process.nextTick 在同一阶段立即触发
  • setImmediate fires on the following iteration or ‘tick’ of the event loop (在事件循环接下来的阶段迭代中执行 - check 阶段)。

nextTick 在事件循环中的位置

1⌚️
2 ┌───────────────────────────┐
3 ┌─>│ timers │
4 │ └─────────────┬─────────────┘
5 │ nextTickQueue
6 │ ┌─────────────┴─────────────┐
7 │ │ pending callbacks │
8 │ └─────────────┬─────────────┘
9 │ nextTickQueue
10 │ ┌─────────────┴─────────────┐
11 | | idle, prepare │
12 | └─────────────┬─────────────┘
13 nextTickQueue nextTickQueue
14 | ┌─────────────┴─────────────┐
15 | │ poll │
16 │ └─────────────┬─────────────┘
17 │ nextTickQueue
18 │ ┌─────────────┴─────────────┐
19 │ │ check │
20 │ └─────────────┬─────────────┘
21 │ nextTickQueue
22 │ ┌─────────────┴─────────────┐
23 └──┤ close callbacks │
24 └───────────────────────────┘

Microtasks 微任务

在 Node 领域,微任务是来自以下对象的回调:

  1. process.nextTick()
  2. then()

在主线结束后以及事件循环的每个阶段之后,立即运行微任务回调。

resolved 的 promise.then 回调像微处理一样执行,就像 process.nextTick 一样。 虽然,如果两者都在同一个微任务队列中,则将首先执行 process.nextTick 的回调。

优先级 process.nextTick > promise.then

看代码输出顺序

1async function async1() {
2 console.log("async1 start");
3 await async2();
4 console.log("async1 end");
5}
6async function async2() {
7 console.log("async2");
8}
9console.log("script start");
10setTimeout(function () {
11 console.log("setTimeout0");
12 setTimeout(function () {
13 console.log("setTimeout1");
14 }, 0);
15 setImmediate(() => console.log("setImmediate"));
16}, 0);
17
18process.nextTick(() => console.log("nextTick"));
19async1();
20new Promise(function (resolve) {
21 console.log("promise1");
22 resolve();
23 console.log("promise2");
24}).then(function () {
25 console.log("promise3");
26});
27console.log("script end");

我们先按运行主线一步一步往下走

js运行主线

  1. 遇到两个异步函数声明,async function, 还没有调用,所以不执行
  2. 遇到console.log(‘script start’),执行
  3. 遇到setTimeout,把回调任务塞入异步任务回调队列,等待
  4. 遇到process.nextTick,塞入微任务回调队列
  5. 执行async1, 执行 console.log(‘async1 start’)
  6. 遇到await async2, 执行 async2, 执行 console.log(‘async2’)
  7. await后面的代码相当于是promise.then,把其塞入微任务回调队列
  8. async1 执行完后遇到了new Promise, 立即执行其中的代码,执行 console.log(‘promise1’)
  9. 遇到了resolve,立即当前函数体的下面代码,执行 console.log(‘promise2’)
  10. 遇到了promise.then, 把其塞入微任务回调队列
  11. 执行 console.log(‘script end’), 此时主线运行完毕,即将进行下一轮事件循环

回调队列

  1. 检查微任务回调队列,如果有,则依次执行并清空微任务队列 依次执行 console.log(‘nextTick’) console.log(‘async1 end’) console.log(‘promise3’)
  2. 检查timmer回调,如果有,则依次执行并清空timmer回调任务队列 (setTimeout和setImmediate在同一任务时,先执行setImmediate) 依次执行 console.log(‘setTimeout0’) console.log(‘setImmediate’) console.log(‘setTimeout1’);

最终执行顺序为

1// js运行主线
2console.log('script start')
3console.log('async1 start')
4console.log('async2 start')
5console.log('promise1')
6console.log('promise2')
7console.log('script end')
8
9// 微任务回调
10console.log('nextTick')
11console.log('async1 end')
12console.log('promise3')
13
14// timmer回调
15console.log('setTimeout0')
16console.log('setImmediate')
17console.log('setTimeout1');

More articles from Favori 重剑

Buffer

nodejs中的内存管理

August 10th, 2021 · 1 min read

微前端解决方案-qiankun

目前国内最好的微前端解决方案-qiankun

August 10th, 2021 · 1 min read
© 2016–2023 Favori 重剑
Link to $https://github.com/yuanguandongLink to $https://favori.zcool.com.cn/Link to $https://codepen.io/favori