问题

考察如下代码,脑回路中运行并输出结果:

console.log("1");

setTimeout(function setTimeout1() {

console.log("2");

process.nextTick(function nextTick1() {

console.log("3");

});

new Promise(function promise1(resolve) {

console.log("4");

resolve();

}).then(function promiseThen1() {

console.log("5");

});

setImmediate(function immediate1() {

console.log("immediate");

});

}); process.nextTick(function nextTick2() {

console.log("6");

}); function bar() {

console.log("bar");

} async function foo() {

console.log("async start");

await bar();

console.log("async end");

} foo(); new Promise(function promise2(resolve) {

console.log("7");

resolve();

}).then(function promiseThen2() {

console.log("8");

}); setTimeout(function setTimeout2() {

console.log("9"); new Promise(function promise3(resolve) {

console.log("11");

resolve();

}).then(function promiseThen3() {

console.log("12");

}); process.nextTick(function nextTick3() {

console.log("10");

});

});

JS 事件循环

JS 是单线程,朴素地讲,同时只能完成一件事件。如果有耗时的任务,那后续的所有任务都要等待其完成才能执行。

为了避免这种阻塞,引入了事件循环。即,将代码的执行分成一个个很小的阶段(一次循环),每个阶段重复相应的事情,直到所有任务都完成。

一个阶段包含以下部分:

  • Timers:到期的定时器任务,setTimeoutsetInterval 等注册的任务。
  • IO Callbacks:IO 操作,比如网络请求,文件读写。
  • IO Polling:IO 任务的注册
  • Set Immediate:通过 setImmediate 注册的任务
  • Close:close 事件的回调,比如 TCP 的断开。

Ticks and Phases of the Node.js Event Loop 图片来自 Daniel Khan 的 Medium 博客,见文末

同步代码及上面每个环节结束时都会清空一遍微任务队列,记住这点很重要!

代码执行流程

执行的流程是,

  • 将代码顺序执行。
  • 遇到异步任务,将任务压入待执行队列后继续往下。
  • 完成同步代码后,检查是否有微任务(通过 Promiseprocess.nextTickasync/await 等注册),如果有,则清空。
  • 清空微任务队列后,从待执行队列中取出最先压入的任务顺序执行,重复步骤一。

另,

  • async/await 本质上是 Promise,所以其表现会和 Promise 一致。
  • process.nextTick 注册的回调优先级高于定时器。
  • setImmediate 可看成 Node 版本的 setTimeout,所以可与后者同等对待。

示例代码分析

Round 1

  • 首先遇到同步代码 console.log(1),立即执行输出 1
  • 接下来是一个 setTimeout 定时器,将其回调压入待执行队列 [setTimeout1]
  • 遇到 process.nextTick,将其回调 nextTick2 压入微任务队列 [nextTick2]
  • 然后是 async 函数 foo 的调用,立即执行并输出 async start
  • 然后是 await 语句,这所在的地方会创建并返回 Promise,所以这里会执行其后面的表达式,也就是 bar() 函数的调用。
  • 执行 bar 函数,输出 bar
  • 在执行了 await 后面的语句后,它所代表的 Promise 就创建完成了,foo 函数体后续的代码相当于 promise 的 then,放入微任务队列 [nextTick2, rest_of_foo]
  • 继续往下遇到 new Promise,执行 Promise 的创建输出 7,将它的 then 回调压入微任务队列 [nextTick2, rest_of_foo,promiseThen2]
  • 遇到另一个 setTimeout,回调压入待执行队列 [setTimeout1,setTimeout2]
  • 至此,代码执行完了一轮。此时的输出应该是 1, async start, bar,7

Round 2

  • 查看微任务队列,并清空。所以依次执行 [nextTick2, rest_of_foo,promiseThen2],输出 6,async end,8

Round 3

  • 查看待执行队列 [setTimeout1,setTimeout2],先执行 setTimout1
  • 遇到 console.log(2) 输出2
  • 遇到 process.nextTicknextTick1 压入微任务队列 [nextTick1]
  • 遇到 new Promise 立即执行 输出 4,执行 resolve() 后将 promiseThen1 压入微任务队列 [nextTick1,promiseThen1]
  • 遇到 setImmediate 将回调压入待执行队列 [setTimeout2,immediate1]
  • 此时 setTimeout1 执行完毕,此时的输出应该为 2,4

Round 4

  • 检查微任务队列 [nextTick1,promiseThen1] 依次执行并输出 3,5

Round 5

  • 检查待执行队列 [setTimeout2,immediate1],执行 setTimeout2
  • 遇到 console输出 9
  • 遇到 new Promise 执行并输出 11,将 promiseThen3 压入微任务队列 [promiseThen3]
  • 遇到 process.nextTicknextTick3 压入微执行队列。注意,因为 process.nextTick 的优化级高于 Promise,所以压入后的结果是: [nextTick3,promiseThen3]
  • 此时 setTimeout2 执行完毕,输出为 9,11

Round 6

  • 检查微任务队列 [nextTick3,promiseThen3] 执行并输出 10,12

Round 7

  • 检查待执行队列 [immediate1],执行并输出 immediate

至此,走完了所有代码。

结果

以下是文章开头的结果:

1
async start
bar
7
6
async end
8
2
4
3
5
9
11
10
12
immediate

参考

理解 Node.js 的 Event loop的更多相关文章

  1. 浅析Node.js的Event Loop

    目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...

  2. 定时器setTimeout()和Node.js的Event Loop

    一.定时器 setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行.它在"任务队列"的尾部添加一个事件,因此要等到同步任务和 ...

  3. 【Node.js】Event Loop执行顺序详解

    本文基于node 0.10.22版本 关于EventLoop是什么,请看阮老师写的什么是EventLoop 本文讲述的是EventLoop中的执行顺序(着重讲setImmediate, setTime ...

  4. Node.js学习 - Event Loop

    Node.js本身是单线程,但通过事件和回调支持并发,所以性能非常高. Node.js的每一个API都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. 事件驱动程序 实例 var ev ...

  5. 理解Node.js的事件轮询

    前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书,亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操 ...

  6. 方便大家学习的Node.js教程(一):理解Node.js

    理解Node.js 为了理解Node.js是如何工作的,首先你需要理解一些使得Javascript适用于服务器端开发的关键特性.Javascript是一门简单而又灵活的语言,这种灵活性让它能够经受住时 ...

  7. [NodeJs系列][译]理解NodeJs中的Event Loop、Timers以及process.nextTick()

    译者注: 为什么要翻译?其实在翻译这篇文章前,笔者有Google了一下中文翻译,看的不是很明白,所以才有自己翻译的打算,当然能力有限,文中或有错漏,欢迎指正. 文末会有几个小问题,大家不妨一起思考一下 ...

  8. 深入理解Node.js中的垃圾回收和内存泄漏的捕获

    深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...

  9. 如何理解Node.js和JavaScript的关系

    一.Javascript的引擎 浏览器一般有两个引擎,一个是Html引擎,一个是脚本引擎. JavaScript是一种脚本语言,最初用于浏览器的动态显示,方便操作页面数据和内容.但实际上,它也可以在浏 ...

随机推荐

  1. Ext Js v6.2.0.103 Sencha Cmd 命令

    Sencha Cmd v6.2.0.103 Sencha Cmd 提供几种全局开关命令. 在大多数案例中, 第一步是在Sencha SDK基础上创建应用 例如 Ext JS 或 Sencha Touc ...

  2. 面向对象的WebAPI框架XXL-HEX

    <面向对象的WebAPI框架XXL-HEX>    一.简介 1.1 概述 XXL-HEX 是一个简单易用的WebAPI框架, 拥有 "面向对象.数据加密.跨语言" 的 ...

  3. Qt中绘制五子棋棋盘

    一个需要做大作业的同学问我相关内容,就顺手写了一个,贴出来. 项目包含头文件 mainwindowh,源文件mainwindow.cpp和主函数main.cpp. 如下: mainwindow.h # ...

  4. 「速成应用」实在可靠的 微信小程序第三方代理加盟平台公司

    小程序,是基于微信平台的一个划时代产品,也就是嵌入到微信里的一个功能丰富.操作简洁的轻应用,不需要下载安装即可使用.不同的小程序,能实现不同的功能.例如,买电影票.餐厅排号.餐馆点菜.查询公交.查询股 ...

  5. python字符串基本编码

    综述:python中字符串分为字节字符和非字节字符python3中默认输入字符串以非字节字符编码,使用unicode字符集表示,可以使用encode方法转化为ascii,utf-8, utf-16等各 ...

  6. Spring对象生存周期(Scope)的分析

    一.Scope定义 Scope用来声明容器中管理的对象所应该处的限定场景或者说对象的存活时间,即容器在对象进入相应的Scope之前,生产并装配这些对象,在该对象不再处于这些Scope之后,容器通常会销 ...

  7. 第六章——决策树(Decision Trees)

    决策树是强大的,多功能的机器学习算法. 6.1 训练和可视化一个决策树 在iris数据集训练DecisionTreeClassifier: from sklearn.datasets import l ...

  8. pymongo的使用

    利用python操作mongodb需要导入pymongo库   pip install pymongo 1.连接mongodb     import pymongo     client = pymo ...

  9. JavaScript Math 对象的常用方法

    JavaScript Math 对象 Math 对象 Math 对象用于执行数学任务. 使用 Math 的属性和方法的语法: var pi_value=Math.PI; var sqrt_value= ...

  10. Spring_boot入门(1)

    Spring boot 将很多东西都集成在一起了,搭建maven项目的时候只需要引入很少的依赖就可以实现项目的搭建. 1.搭建maven项目结构 2.引入Spring boot 依赖 直接去官网找就可 ...