问题

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

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. java线程之线程同步

    本篇由于涉及多线程操作,所以线程是使用实现Runnable接口来创建的. 在上篇所示线程任务中,我们不难发现,是存在三步操作的: 第一:打印语句: 第二:计算sum=sum-1: 第三:线程休眠. 那 ...

  2. SQL转化为MapReduce的过程

    转载:http://www.cnblogs.com/yaojingang/p/5446310.html 在了解了MapReduce实现SQL基本操作之后,我们来看看Hive是如何将SQL转化为MapR ...

  3. Python_struct模块操作二进制文件

    ''' 使用struct模块写入二进制文件 ''' import struct n=130000000 x=96.45 b=True s='a1@中国' sn=struct.pack('if?',n, ...

  4. linux dialog详解(图形化shell)

    liunx 下的dialog 工具是一个可以和shell脚本配合使用的文本界面下的创建对话框的工具.每个对话框提供的输出有两种形式:   1.  将所有输出到stderr 输出,不显示到屏幕.   2 ...

  5. Java公开课-03.内部类

    一.内部类的作用 1.实现了类的隐藏 2.实现了多重继承 3.内部类拥有外部类所拥有的属性和方法的访问权限 4.避免修改接口的时候出现同名方法 二.内部类--成员内部类 1.如果我们想访问内部类,我们 ...

  6. Http Hijacker

  7. How nginx "location if" works

    Nginx's if directive does have some weirdness in practice. And people may misuse it when they do not ...

  8. LeetCode Javascript实现 258. Add Digits 104. Maximum Depth of Binary Tree 226. Invert Binary Tree

    258. Add Digits Digit root 数根问题 /** * @param {number} num * @return {number} */ var addDigits = func ...

  9. Nginx多虚拟主机下泛域名配置

    http://www.tuicool.com/articles/F3Azuq 近上一个应用,让用户可以自定义二级域名,所以要配置一个泛域名来解析用户的自定义域名.  首先来说说nginx下的泛域名配置 ...

  10. goroutine背后的系统知识

    http://www.sizeofvoid.net/goroutine-under-the-hood/ o语言从诞生到普及已经三年了,先行者大都是Web开发的背景,也有了一些普及型的书籍,可系统开发背 ...