问题

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

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. JavaScript中对数组和数组API的认识

    JavaScript中对数组和数组API的认识 一.数组概念: 数组是JavaScript中的一类特殊的对象,用一对中括号“[]”表示,用来在单个的变量中存储多个值.在数组中,每个值都有一个对应的不重 ...

  2. Binary Search 的递归与迭代实现及STL中的搜索相关内容

    与排序算法不同,搜索算法是比较统一的,常用的搜索除hash外仅有两种,包括不需要排序的线性搜索和需要排序的binary search. 首先介绍一下binary search,其原理很直接,不断地选取 ...

  3. QT窗体的小技巧

    1.界面透明 setWindowOpacity(0.8);//构造函数中加此句,1为不透明,0为完全透明,0.8为80%不透明. 2.设置背景图片 QPixmap pixmap = QPixmap(& ...

  4. shiro 权限管理配置

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  5. 初学JSP

    一. 基本了解     JSP是应用最广泛的表现层技术,它和Servlet是Java EE的两个基本成员.JSP和Servlet本质是一样的,因为JSP最终编译成ServLet才能运行. 1.1 we ...

  6. 关于office在卸载了某一应用之后无法试图使用的功能所在的网络位置

    我出现这个问题是在卸载了某一个微软的办公软件之后,所有的办公软件都会产生这个问题. 处理的方法是将之前的安装包解压,然后找到所出现的msi文件,点击确定就ok了. 所以说,安装文件最好还是放在一个地方 ...

  7. VC++中字符串编码处理的一些相关问题

    前言 什么是tchar? 百度百科对其的定义如下": 因为C++支持两种字符串,即常规的ANSI编码(使用""包裹)和Unicode编码(使用L""包 ...

  8. Netty中的EventLoop和线程模型

    一.前言 在学习了ChannelHandler和ChannelPipeline的有关细节后,接着学习Netty的EventLoop和线程模型. 二.EventLoop和线程模型 2.1. 线程模型 线 ...

  9. Android的JDK、SDK、Eclipse的理解

    今天看了这方面的内容,感觉学到了一些东西: 首先,jdk是用来处理Java语言的, sdk是用来处理Java语言和硬件之间的关联的, eclipse是用来编写Java语言的, 通过对这方面的理解,加深 ...

  10. Spring-cloud (八) Hystrix 请求缓存的使用

    前言: 最近忙着微服务项目的开发,脱更了半个月多,今天项目的初版已经完成,所以打算继续我们的微服务学习,由于Hystrix这一块东西好多,只好多拆分几篇文章写,对于一般对性能要求不是很高的项目中,可以 ...