【异步系列五】关于async、await、promise、微任务、宏任务的执行顺序解析【最终篇】

news/2024/4/28 11:18:22/文章来源:https://blog.csdn.net/qq_41131745/article/details/127582980

前段时间总结了几篇关于异步原理、Promise原理、Promise面试题、async/await 原理的文章,链接如下,感兴趣的可以去看下,相信会有所收获。

一篇文章理清JavaScript中的异步操作原理

Promise原理及执行顺序详解

10道 Promise 面试题彻底理解 Promise 异步执行顺序

async await 原理解析之爱上 async/await

本篇文章准备一个代码实例来阐述async/await、promise、setTimeout(宏任务、微任务)之间的执行顺序,做一个最终总结。理论终究是理论,枯燥难懂,对于程序猿来说,最好的还是代码实例。所以就找了一个非常有代表性的面试题。

目标:不是写出正确的执行顺序,而是说清楚每一个步骤,为什么这么执行。


async function async1 () {console.log('async1 start')await async2()console.log('async1 end')
}async function async2 () {console.log('async2')
}console.log('script start')setTimeout(function() {console.log('setTimeout')
}, 0)async1()new Promise(function (resolve) {console.log('proimse1')resolve()
}).then(function() {console.log('promise2')
})
console.log('script end')

这个代码实例算是很经典的一道题了,其中涉及到了 js 的 eventloop、promise、async、await 以及定时器。

1. 注意点

很显然,这考察的是 JavaScript 中的 事件循环回调队列 ,需要注意以下几点:

  • Promise 的优先级高于 setTimeout 微任务,所以, setTimeout 回调会在最后执行。
  • Promise 一旦被定义,就会立即执行。
  • Promise resolvereject 是异步执行的回调。所以,resolve 会被放到回调队列中,在主函数执行完 和 宏任务 setTimeout 执行前调用。
  • await 会阻塞后面的任务,指的是下一行代码,await 的同行代码是会立即执行的
  • await 执行完后,会让出线程。async 标记的函数会返回一个 Promise 对象

2. 代码分析

首先分析下代码,发现里面有 同步代码、微任务、宏任务。

一段代码执行时,会先执行宏任务中的同步代码

  • 如果执行中遇到 setTimeout 之类的宏任务,就会把这个 setTimeout 内部的函数放到【宏任务的队列】中,下一轮宏任务执行时调用。
  • 如果执行中遇到 Promise.then() 之列的异步微任务,就会把异步微任务放到【当前宏任务的微任务队列】中,在本轮宏任务的同步代码都执行完成后,依次执行所有的异步微任务:task1、task2、task3…

在这里插入图片描述
在每一层(一次)的事件循环中,首先整体代码块看做一个宏任务,宏任务中的 Promise(then、catch、finally )、MutationObserver、Process.nextTick 就是该宏任务层的微任务。宏任务中的同步代码进入主线程中是立即执行的,宏任务中的非微任务的异步代码(比如定时器)将作为下一次循环时的宏任务进入的调用栈等待执行。此时,调用栈中的等待执行队列分为两种,分别是优先级较高的本层循环中的微任务队列,以及优先级低的下次循环执行的宏任务队列。

每一个宏任务队列都可以理解为当前的主线程,JavaScript 总是先执行主线程上的任务,执行完 毕后执行当前宏任务队列上的所有微任务,先进先出原则,在执行完当前宏任务队列上的所有微任务之后,才会执行下一个宏任务。

3. 执行顺序解析:


执行顺序解析:1、js是单线程的,首先执行主线程上的任务 console.log('script start') 输出:script start2、遇到setTimeout()定时器,这是一个宏任务,放入到下一个宏任务队列中,等待当前宏任务以及其微任务队列执行完毕再执行。3、执行 async1() 函数,实质上是创建了一个Promise对象,而promise的构造函数的运行是在主任务队列中的,所以会立即执行 console.log('async1 start')输出: async1 start4、执行 await async2()。我们知道,await 会立即执行同行代码,阻塞下一行代码,(await 也会暂停async后面的代码,先执行async外面的同步代码)流程进入 async2()函数,并返回 Promise 对象,即返回 async2.then(() => {console.log('async1 end')})。这里就会把 .then() 里面的内容放到当前宏任务的微任务队列中(await 阻塞下一行代码),将其命名为task1.此时task1并没有执行,因为微任务会在当前宏任务的同步代码执行完成后,才会依次执行。同时也会执行 async2() 的构造函数,输出async2 输出:async2ps: 这个地方可能有人会看不懂,请看下面解析。5、执行 new Promise(),当我们 new 一个 Promise 时,传入的回调函数(构造函数)为同步代码,会立即执行。输出:promise16、执行 resolve() 函数,那么会进入到 then() 中,我们知道,Promise.then() 是一个异步微任务,所以会被放到当前宏任务的微任务队列中,,将其命名为task2。此时task2并没有执行,因为微任务会在当前宏任务的同步代码执行完成后,才会依次执行。7、执行最后的主线程任务:console.log('script end')输出:script end8、此时宏任务1中的同步代码已经执行完成,开始依次处理微任务队列中的代码遵循:先进先出原则。输出:async1 end  ; promise29、在最后执行下一个宏任务队列,即setTimeout输出:setTimeout

所以最终输出结果为:

在这里插入图片描述

4. 额外解析

对于上面的流程解析,可能有人对第4步不太理解,首先我们明确一个概念:async/await 实质上是 Promise.then 的语法糖,带 async 关键字的函数,会让函数返回一个 Promise 对象。
其实,async1() 函数 可以写成以下方式,便于理解:

async function async1() {console.log('async1 start')async2().then(_ => {console.log('async1 end')})
}

如果 return 的不是 promise,会自动用 Promise.resolve() 包装,就以代码实例中的 async2() 为例,返回的就是 return Promise.resolve(undefined)

如果 async 关键字函数显式的返回 Promise,则以此为准。

对于 await 来说,如果 await 后面不是 Promise 对象,那么 await 会阻塞后面的代码,先执行 async 函数外面的同步代码,同步代码执行完毕后,再回到 async 内部,把这个 非 Promise 的东西,作为 await 表达式的结果,然后在执行下面的代码。

如果 await 后面是 Promise 对象,await 也会阻塞后面的代码,在 async 外部的同步代码执行完成之后等到 promise 对象 fulfilled,然后把 resolve 的参数作为await 表达式的运行结果

5. 分析下 await

我们知道,await 会让出线程,阻塞后面的代码。那么在代码执行中,是一旦碰到 await 直接跳出,阻塞 async2() 的执行?还是先执行 async2() ,发现有 await 关键字,于是让出线程,阻塞代码呢?

从上面的实践可以得出结论:代码是从右向左执行,先执行,发现有await关键字后,让出线程,阻塞代码。

结论:

  • . 它先计算出右侧的结果
    • 看到 await 后,中断 async 函数,先执行 async 外的同步代码

7. 结尾

我的异步系列更新到这里基本上就算完结了,不过学习永不结束!

我把上面的代码实例顺序稍微改动了下,大家可以看下这个会输出什么结果,如果上述解析看懂了的话,下面的代码也是相当简单,答案可以写在评论区,欢迎大家讨论、交流。


new Promise(function (resolve) {console.log('proimse1')resolve()
}).then(function() {console.log('promise2')
})async function async1 () {console.log('async1 start')await async2()console.log('async1 end')
}async function async2 () {console.log('async2')
}async1()console.log('script start')setTimeout(function() {console.log('setTimeout')
}, 0)console.log('script end')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_410329.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

输入输出、文件读写、数据类型

package chapter01 /* object:关键字,声明一个单例对象(伴生对象)*/ object HelloWorld {/*main方法:从外部可以直接调用执行的方法def 方法名称(参数名称:参数数据类型):方法返回值类型 { 方法…

2.8 标准输入与格式化输出

文章目录1. Input 标准输入1.1 标准输入1.2 阻塞状态1.3 输入提示1.4 获取输入字符串1.5 输入版本差异1. Python3 输入数据类型2. Python2 输入数据类型2. Print 格式化输出2.1 输入2.2 sep 参数2.3 end 参数2.4 快捷写法2.5 格式化输出1. 语法格式2. 字典形式传值3. 元组形式传…

什么是GPT

什么是GPT 参考资料: https://zhuanlan.zhihu.com/p/350017443 https://zhuanlan.zhihu.com/p/106462515 https://www.cnblogs.com/yifanrensheng/p/13167796.html https://blog.csdn.net/weixin_45577864/article/details/119651372 Generative Pre-trained T…

这可能是你需要的vue考点梳理

对 React 和 Vue 的理解,它们的异同 相似之处: 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;都使用了Virt…

Golang学习之路3-基础认识(下)

文章目录前言一、数组1.定长数组2.不定长数组二、map1.使用关键字 map 来声明2.使用 make 来声明3.添加元素4.检索key的value是否存在5.删除元素6.遍历map7.map的注意点在这里插入图片描述三、指针1.使用指针& 及 *2.空指针四、循环与条件判断1.循环2.条件判断前言 学习一…

Go语言函数

什么是函数 func main() {fmt.Println("hello,world")//调用函数fmt.Println(add(1, 2)) }// func 函数名(参数,参数。。。),函数调用返回值类型() func add(a, b int) int {c : a breturn c }函…

Ray tracing 光线追踪 之 embree ,从入门到精通 02 从源码编译与安装

1. 下载预编译的ispc,安装 网址: https://ispc.github.io resources -> github page 进入ispc 的github的release页:Releases ispc/ispc GitHub 找到一个预编译好了的ispc,其中在windows平台上是:https://github…

Redis缓存穿透、击穿、雪崩介绍

面试高频,工作常用 缓存穿透(查不到) 概念 用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败,当用户很多的…

GO实现跳跃表

GO实现跳跃表 文章目录GO实现跳跃表跳跃表介绍跳跃表的实现跳跃表的结构创建跳跃表跳跃表的插入和删除跳跃表的排名操作跳跃表的区间操作完整实现跳跃表介绍 跳跃表(skiplist)是一种有序的数据结构,它通过建立多层"索引"&#xff…

世界城市日|数字城市里看不见的“保安”,真面目竟是…

2022年10月31日,是第8个世界城市日。在数字化浪潮席卷全球的当下,城市发展亦进入新的阶段。建造数字城市,全面推进城市数字化转型成为当前城市建设的热议话题。数字城市、万物互联,与网络空间的融合必不可少。然而系统的复杂度越高…

简单使用gige千兆网口工业相机,国产崛起(二,c#)

发现海康的sdk不错,可以用海康,basler,大华工业相机,估计其他的也可以,有机会试一试!国产厉害,崛起了!赞一个,热情爆棚!且随窃喜! 首先下载海康工…

网站SEO标题撰写技巧,做到这些可以提高点击率

搜索引擎认为,一个网站的点击率越高,那么这个网站就越受欢迎,因此就会提高网站的关键词排名。网站的点击率越高,就会获得更多流量。网站标题和点击率息息相关,一个好的网站标题,能够轻松获得流量。那么&…

[carla入门教程]-2 pythonAPI的使用

本专栏教程将记录我从安装carla到调用carla的pythonAPI进行车辆操控的全流程,带领大家从安装carla开始,到最终能够熟练使用carla仿真环境进行传感器数据采集和车辆控制. 第二节 pythonAPI的使用 本小节主要学习使用 pythonAPI来与carla服务器进行交互.包括获取信息,发送信息.…

IDEA热部署插件JRebel使用

JRebel安装与激活 JRebel 使用 此时已经安装好并已激活,我们使用 JRebel debug的时候,修改代码,不能实现热部署,因此还需要设置其他地方 1.项目自动编译 设置 compiler.automake.allow.when.app.running ctrlshiftA 或者 help->…

vue相关原理

vue 原理 面试为什么要考察原理 知其然知其所以然,各行各业通用的道理了解原理才能用的很好,专业性考察,技术的追求竞争激烈,则优录取大厂造轮子(业务定制:有些框架不能满足需求) 面试中如何…

【Spark NLP】第 19 章:生产化 NLP 应用程序

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃 🎁欢迎各位→点赞…

docker下快速部署openldap与PHPLdapAdmin

在一个组织中,为了简化各种内部系统的账号和密码的管理,往往就需要ldap来进行管理了。 对于ldap的实现方式也非常多,但在免费的开源系统中,openldap是ldap的首选系统。 同时,在这一切讲究快速的时代,采用d…

大数据ClickHouse进阶(二十二):ClickHouse优化

文章目录 ClickHouse优化 一、表优化 1、日期字段避免使用String存储 2、Nullable值处理 <

计算机毕业设计(附源码)python音蕾心动

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

云IDE的简单使用、体验与学习

云IDE的简单使用、体验与学习一、简单尝试二、官网展示的特点三、视频用例3.1、用Cloud IDE快速启动开源项目3.2、用Cloud IDE 在线提交PR云IDE产品介绍 云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间。 一、简单尝试 快速创建工作空…