前言

写此之前,也查阅了很多文章,并结合自己的理解,说说对Event Loop模型的理解、以及对Promise、async/await在任务队列中的影响进行了分析,也给出了多种情形的任务案例以及分析解释,相信大家看完会有所收获;当然,也是自己的理解,难免有所偏差,欢迎大家指正~

一、 宏任务、微任务的基本概念

  1. 宏任务、微任务发展及出现的原因:

JavaScript是典型的单线程(自上而下依次执行代码),但是,一个任务耗时过长,或多个任务需要执行时,势必导致线程阻塞,影响视图渲染效果。

在ES3以及以前的版本中,JavaScript本身没有异步任务能力,随着Promise的引入,JavaScript引擎自身也能够发起异步任务了。至此,JavaScript就可分为同步任务及异步任务;而JS又把异步任务做了进一步的划分,分为宏任务与微任务。

由于微任务执行快,一次性可以执行很多个,在当前宏任务执行后立刻清空微任务可以达到伪同步的效果,这对视图渲染效果起到至关重要的作用,这也是区分宏任务、微任务的原因。

  1. 宏任务、微任务的基本类型

宏任务:

  • script(外层同步代码)

  • Ajax请求

  • setTimeout、setInterval

  • postMessage、MessageChannel

  • setImmediate(Node.js 环境)、I/O(Node.js 环境)

  • ...

微任务

  • Promise.then().catch() 和 .finally()

  • process.nextTick(Node.js 环境)

  • ...

二、 事件循环模型(Event Loop)

如上图,当同步代码执行完毕后,就会执行所有的宏任务,宏任务执行完成后,会判断是否有可执行的微任务;如果有,则执行微任务,完成后,执行宏任务;如果没有,则执行新的宏任务,形成事件循环。

这只是图示宏任务及微任务的执行关系,那么,js在 Event Loop中究竟是如何调用方法去处理的呢?

  1. 我们写的代码,js会进行任务类型分配,根据类型放入不同的任务队列中;

  1. Event Loop 会根据执行时机,将需要执行的函数,压入事件调用堆栈中执行;

  1. 相同的宏任务,也会有不同的执行时机,(类似 setTimeout 的 time),Event Loop 会控制将需要执行的函数压入。

总结:Event Loop 在压入事件时,都会判断微任务队列是否还有需要执行的事件:如果有,则优先将需要执行的微任务压入;没有,则依次压入需要执行宏任务!

切记,宏任务执行完毕后,都会判断是否还有需要执行的微任务!!!在复杂的事件中,该点经常会错!!!

切记,宏任务执行完毕后,都会判断是否还有需要执行的微任务!!!在复杂的事件中,该点经常会错!!!

切记,宏任务执行完毕后,都会判断是否还有需要执行的微任务!!!在复杂的事件中,该点经常会错!!!

三、 Promise、async和await 在事件循环中的处理

  1. Promise:

new Promise 创建实例的过程是同步的哦!

 console.log(1);new Promise((resolve, reject) => {console.log(2);})console.log(3);
// 结果: 1 2 3

但是,Promise.then().catch().finally()中的回调,是微任务。

 console.log(1);new Promise((resolve, reject) => {console.log(2);resolve(); // 触发 then 回调// reject(); // 触发 catch 回调}).then(()=>{console.log('then')}).catch(()=>{console.log('catch')
}).finally(()=>{console.log('finally')
})console.log(3);
// 结果: 1 2 3 then finally

上图是菜鸟教程-JavaScript Promise 中对Promise的讲解,我们想一下为啥要这样设计:

我们假设Promise 不是立即执行的,会有什么后果?利用Promise,多是封装异步请求(Ajax),而请求不是立即请求的,还需要等待Promise 任务执行,那么我们就失去了网络请求的时效性,会导致页面等待渲染(因为我们上面提及的 Event Loop 会根据事件执行时机,选择将事件压入堆栈中,我们无法保证自己写的【不是立即执行Promise】什么时候执行)。

  1. async/await :

简单来说,async是通过Promise包装异步任务。

async function fun1() {console.log('fun1 start')await fun2(); // 等待 fun2 函数执行完成console.log('fun1 end')
}
async function fun2() {console.log('fun2 start')console.log('fun2 end')
}
fun1()// 输出结果: fun1 start、fun2 start、fun2 end、fun1 end

遇到 await 则需要等待 await 后的代码执行完成后,在往下执行代码。因此,可以将 await 看作抢夺线程的标记,fun1 中,本来是同步执行的,但是 await 的出现,导致线程执行了 fun2 的代码后,再次回到await,往后执行。

同时,await后面的代码,会进入then微任务中!!!

同时,await后面的代码,会进入then微任务中!!!

同时,await后面的代码,会进入then微任务中!!!

async function fun1() {console.log('fun1 start')await fun2(); // 等待 fun2 函数执行完成console.log('我是 await 后面的代码')console.log('fun1 end')
}
async function fun2() {console.log('fun2 start')console.log('fun2 end')
}
fun1()
console.log('await 阻塞,导致 await后面代码进入 then 微任务')

这个知识点比较容易忽略,以为 await 回来后,直接继续执行后面的代码,这是不对的!

四、 process.nextTick在事件循环中的处理

process.nextTick是Node环境的变量,process.nextTick() 是一个特殊的异步API,其不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。所以,nextTick和Promise同时出现时,肯定是process.nextTick() 先执行。

可以类比 Vue.$nextTick(),也是需要执行完这个函数后,才能继续Event Loop。

五、 典型案例

5.1 典型的宏任务、微任务

setTimeout(function () {console.log('1');
});new Promise(function (resolve) {console.log('2');resolve();
}).then(function () {console.log('3');}).then(function () {console.log('4')});console.log('5');

5.2 宏任务中包含微任务

setTimeout(function () {console.log('1');new Promise(function (resolve) {console.log('2');resolve();}).then(function () {console.log('3');})console.log('4');
});console.log('5');

5.3 微任务中包含宏任务

console.log('1');
new Promise(function (resolve) {console.log('2');resolve();
}).then(function () {console.log('3');setTimeout(function () {console.log('4');})console.log('5');})console.log('6');

5.4 async 宏任务

async function fun1() {console.log('fun1 start')setTimeout(function () {console.log('fun1 setTimeout');});await fun2();console.log('fun1 end')
}async function fun2() {console.log('fun2 start')new Promise((resolve)=>{console.log('fun2 Promise')resolve()}).then(()=>{console.log('fun2 Promise then')})console.log('fun2 end')
}fun1()

5.5 node 事件执行分析

console.log('1');setTimeout(function() {console.log('2');process.nextTick(function() {console.log('3');})new Promise(function(resolve) {console.log('4');resolve();}).then(function() {console.log('5')})
})
process.nextTick(function() {console.log('6');
})
new Promise(function(resolve) {console.log('7');resolve();
}).then(function() {console.log('8')
})setTimeout(function() {console.log('9');process.nextTick(function() {console.log('10');})new Promise(function(resolve) {console.log('11');resolve();}).then(function() {console.log('12')})
})

5.6 综合案例

console.log('script start')async function fun1() {console.log('fun1 start')process.nextTick(function() {console.log('fun1 process nextTick');})setTimeout(function () {console.log('fun1 setTimeout');new Promise(function (resolve) {console.log('fun1 Promise');resolve();}).then(function () {console.log('fun1 Promise then');setTimeout(function () {console.log('fun1 Promise then setTimeout');})console.log('fun1 Promise then end');})});await fun2();console.log('fun1 end')
}async function fun2() {console.log('fun2 start')setTimeout(function () {console.log('fun2 setTimeout');});new Promise((resolve)=>{console.log('fun2 Promise')resolve()}).then(()=>{console.log('fun2 Promise then')})console.log('fun2 end')
}fun1()setTimeout(function() {console.log('setTimeout-000')
}, 0)new Promise(resolve => {console.log('Promise')process.nextTick(function() {console.log('Promise process nextTick');})resolve()
}).then(function() {console.log('promise1')}).then(function() {console.log('promise2')process.nextTick(function() {console.log('promise2 process nextTick');})})console.log('script end')

六、案例分析

6.1 典型的宏任务、微任务

 // 同步任务console.log('2'); // new promise 实例化过程console.log('5');// // 将 setTimeout console.log('1'); 放入宏任务队列// 将Promise 的回调放入微任务队列// then  console.log('3');// then  console.log('4')// 先微任务console.log('3');console.log('4')
// 再宏任务console.log('1');// 因此,输出结果: 2 5 3 4 1

6.2 宏任务中包含微任务

// 同步任务
console.log('5');
// 将 setTimeout 放入宏任务队列,此时,没有微任务执行,因此,开始执行setTImeout宏任务
console.log('1');
// new Promise 实例化 同步执行:
console.log('2');
// 将Promise.then 回调放入微任务
// 当前(setTimeout)的宏任务事件还没有执行完!!!
// 注意哈!!当前(setTimeout)的宏任务事件还没有执行完!!!,事件未跳出当前 Loop
console.log('4');
// 执行完宏任务,开始执行 微任务
console.log('3');// 因此,结果为: 5 1 2 4 3

6.3 微任务中包含宏任务

 // 同步代码:
console.log('1');
// new Promise
console.log('2');
console.log('6');
// 微任务:Promise.then
console.log('3');
console.log('5');
// 结束当前 Loop[有上个例子,这个就不难理解了]
// 开启宏任务
console.log('4');// 因此,结果为:1 2 6 3 5 4

6.4 async 宏任务

 // fun1
console.log('fun1 start')
// 将setTimeout 放入宏任务队列
//  await fun2(); 进入 fun2
console.log('fun2 start')
//   new Promise
console.log('fun2 Promise')
// 将 then 放入微任务
console.log('fun2 end') // 当前任务队列// 有微任务,先执行微任务
console.log('fun2 Promise then')
// 回到 await 处
console.log('fun1 end') // 当前 fun1 队列
console.log('fun1 setTimeout'); // 最后的宏任务

6.5 node 事件执行分析

// 从上往下:
console.log('1'); // 同步代码
//  setTimeout 宏任务1
//  process.nextTick 微任务1
console.log('7'); // new Promise
// Promise.then 微任务2
// setTimeout 宏任务2
// -- 开始执行微任务
console.log('6');
console.log('8')// -- 开始宏任务1
console.log('2');
//  process.nextTick 微任务!!!
console.log('4'); // new Promise
// Promise.then 微任务!!!
// 到此,当前宏任务已执行完毕,有微任务,需要先执行微任务
console.log('3');
console.log('5')// 执行宏任务 2
console.log('9');
//  process.nextTick 微任务
console.log('11');// new Promise
// Promise.then 微任务!!!
console.log('10');
console.log('12')// 因此,结果为:1 7 6 8 2 4 3 5 9 11 10 12

6.6 综合案例

// 这个案例,就应用了 await 导致的 then 微任务细节,我第一次分析也错了
// 还涉及了process.nextTick node 的执行时机优先// 开始分析:console.log('script start')// fun1() 进入 fun1console.log('fun1 start')// 生成微任务 process.nextTick(fun1)// 生成 宏任务 setTimeout (fun1)await fun2(); // 进入 fun2console.log('fun2 start')// 生成宏任务 setTimeout (fun2)console.log('fun2 Promise') // new Promise// 生成 Promise.then 微任务console.log('fun2 end')// !!!此时,fun2 已经有返回值了,不需要等待 fun2 中的事件执行,回到 await 处,被标记了 await .then 的微任务// 因此,执行主任务console.log('Promise') // new Promise// 生成 Promise process.nextTick 微任务// 生成 Promise1.then 微任务// 生成 Promise2.then 微任务console.log('script end')/*** 分析微任务队列* 1. 第一个微任务:process.nextTick(fun1)* 2. fun2 Promise.then // 容易漏* 3. await .then 的微任务 !!!!!!!!!!!!!* 4. Promise process.nextTick 微任务* 5. Promise1.then 微任务* 6. Promise2.then 微任务**/// 根据 Node process 优先级,先执行 processconsole.log('fun1 process nextTick');console.log('Promise process nextTick');console.log('fun2 Promise then')// await.then微任务[await 后的所有代码,如果还有任务,具体再分析即可]console.log('fun1 end')console.log('promise1')console.log('promise2') // 执行到这,又生成新的 process.nextTick 微任务,又先执行console.log('promise2 process nextTick');// 没有微任务了,开始执行宏任务console.log('fun1 setTimeout');console.log('fun1 Promise'); // 生成新的 promise.then 微任务,当前宏任务已执行完成,开始执行微任务console.log('fun1 Promise then'); // 生成新的 宏任务 fun1 Promise then setTimeoutconsole.log('fun1 Promise then end');/*** 此时,分析宏任务队列* 1.  第一个 是 fun2 setTimeout* 2.  setTimeout(function () { console.log('setTimeout-000') }, 0)* 3.  fun1 Promise then setTimeout* */// 因此, 依次执行宏任务console.log('fun2 setTimeout');console.log('setTimeout-000')console.log('fun1 Promise then setTimeout');

这个案例比较复杂,某些事件容易漏掉,因此,建议大家手动勾起来,每一个事件都对应到事件队列中,这个案例,考察两个点,一个是 await的处理及 node.process 的优先级。大家弄懂这个案例,出去面试,手撕这种题目应该不是问题了。

好啦,以上就是对宏任务、微任务、事件循环、案例的整体整理,有不对的地方,欢迎指正呀!

宏任务与微任务执行顺序(超详细讲解)相关推荐

  1. 宏任务和微任务执行顺序_确保任务的执行顺序

    宏任务和微任务执行顺序 有时有必要对线程池中的任务施加一定的顺序. JavaSpecialists通讯的第206期提出了一种这样的情况:我们使用NIO从多个连接中读取数据. 我们需要确保来自给定连接的 ...

  2. 事件循环中的宏任务和微任务执行顺序

    事件循环中的宏任务和微任务执行顺序 先来了解一下事件循环.宏任务.微任务和Promise 1.事件循环(Event Loop)运行机制 执行一个宏任务(栈中没有就从事件队列中获取) 执行过程中如果遇到 ...

  3. 简单易懂的宏任务和微任务执行顺序

    背景 很多朋友在写面试题:宏任务与微任务的执行时机时,容易犯迷糊.之前我也写过两篇文章,这次加上详细的讲解,让大家更容易理解. 什么是宏任务.微任务? 1.首先要称得上是宏任务.微任务的,必须是一个回 ...

  4. JS事件循环中的宏任务和微任务执行顺序

    1. 宏任务和微任务事件 其中微任务的优先级高于宏任务,括号内为事件运行环境 宏任务 微任务 I/O事件/onClick点击事件 process.netTick (Node) setTimeout N ...

  5. Python的零基础超详细讲解(第十二天)-Python函数及使用

    基础篇往期文章: Python的零基础超详细讲解(第一天)-Python简介以及下载_编程简单学的博客-CSDN博客 Python的零基础超详细讲解(第二天)-Python的基础语法1_编程简单学的博 ...

  6. Java基础18-String类【String类的特点对象个数常用方法】【超详细讲解】

    Java基础-String类[超详细讲解] String类的特点 String在java.lang.String包中 1:特点 (1)String类型不能被继承,因为由final修饰 (2)Strin ...

  7. Python的零基础超详细讲解(第七天)-Python的数据的应用

    往期文章 Python的零基础超详细讲解(第一天)-Python简介以及下载_编程简单学的博客-CSDN博客 Python的零基础超详细讲解(第二天)-Python的基础语法1_编程简单学的博客-CS ...

  8. python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上

    Python高级编程--装饰器Decorator超详细讲解(上篇) 送你小心心记得关注我哦!! 进入正文 全文摘要 装饰器decorator,是python语言的重要特性,我们平时都会遇到,无论是面向 ...

  9. mybatis-plus超详细讲解

    (6条消息) mybatis-plus超详细讲解_zdsg45的博客-CSDN博客_mybatis-plushttps://blog.csdn.net/zdsg45/article/details/1 ...

最新文章

  1. 计算机语言主要由一些指令组成这些指令包括,(计算机组成指令系统汇编语言.ppt...
  2. windos10下编译opencv_4.0.1+opencv-contrib_4.0.1
  3. 塔防游戏的路径寻找算法分析
  4. word文档图标变成白纸_挽救你的文件 修复变成乱码的Word文档
  5. POJ - 1847 Tram(dijkstra)
  6. redis 中一个字段 修改map_CTO 指名点姓让我带头冲锋,熬了一个通宵,终于把Redis中7千万个Key删完了...
  7. 重磅!阿里宣布成立芯片公司,“平头哥”能解决中国的“无芯之痛”吗?
  8. 让Jacob从当前路径读取dll文件及相关打包方法
  9. html5页脚最低下,页面底部或内容的页脚,以较低者为准
  10. [Node.js] Module.Require机制研究
  11. linux修改参数命令,linux chfn命令参数及用法详解(linux修改用户资料命令)
  12. GPUImageMovieWriter录制视频问题
  13. SQL Server 游标运用:查看所有数据库所有表大小信息(Sizes of All Tables in All Database)...
  14. git submodule使用的笔记
  15. leetcode中文版python,Python 版 LeetCode 刷题笔记 #7 整数反转
  16. python之爬虫(十一) 实例爬取上海高级人民法院网开庭公告数据
  17. SpringBoot Mongo 动态分表 动态修改表名
  18. 教你如何在电脑上建一个绝密文件夹
  19. 华为交换机dhcp获取不到_S7706交换机客户端无法通过DHCP获取地址问题
  20. 测试分析报告(GB8567——88)基于协同的在线表格forture-sheet

热门文章

  1. 【UE4】材质编辑器教程笔记整理
  2. mdx java_MDX大部分常用函数
  3. 用计算机制作标准曲线的方法,怎么用分光光度计制作标准曲线_制作标准曲线有什么意义...
  4. Ios 仿ibooks 翻页效果
  5. 双足机器人重心在头部_波士顿动力双足机器人Atlas放出逆天体操表演
  6. Linux 设置VGA模式
  7. numpy.random.rand(),numpy.random.randn(),numpy.random.normal()函数介绍和示例
  8. 北理工在线作业计算机的主要特点是( ),18春北理工《用户界面设计》在线作业-2...
  9. Docker镜像报错:Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request cance
  10. 解释器,一个交互式外壳