【异步系列五】关于async/await与promise执行顺序详细解析及原理详解

2023-11-17

前段时间总结了几篇关于异步原理、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 start

	2、遇到setTimeout()定时器,这是一个宏任务,放入到下一个宏任务队列中,等待当前宏任务以及其微任务队列执行完毕再执行。

	3、执行 async1() 函数,实质上是创建了一个Promise对象,而promise的构造函数的运行是在主任务队列中的,所以会立即执行 console.log('async1 start')
				输出: async1 start
				
	4、执行 await async2()。我们知道,await 会立即执行同行代码,阻塞下一行代码,
	(await 也会暂停async后面的代码,先执行async外面的同步代码)
	流程进入 async2()函数,并返回 Promise 对象,即返回 async2.then(() => {console.log('async1 end')})。
	这里就会把 .then() 里面的内容放到当前宏任务的微任务队列中(await 阻塞下一行代码),将其命名为task1.
	此时task1并没有执行,因为微任务会在当前宏任务的同步代码执行完成后,才会依次执行。
	同时也会执行 async2() 的构造函数,输出async2 
				输出:async2

	ps: 这个地方可能有人会看不懂,请看下面解析。
				
	5、执行 new Promise(),当我们 new 一个 Promise 时,传入的回调函数(构造函数)为同步代码,会立即执行。
				输出:promise1
				
	6、执行 resolve() 函数,那么会进入到 then() 中,
	我们知道,Promise.then() 是一个异步微任务,所以会被放到当前宏任务的微任务队列中,,将其命名为task2。
	此时task2并没有执行,因为微任务会在当前宏任务的同步代码执行完成后,才会依次执行。

	7、执行最后的主线程任务:console.log('script end')
				输出:script end

	8、此时宏任务1中的同步代码已经执行完成,开始依次处理微任务队列中的代码
		遵循:先进先出原则。
				输出:async1 end  ; promise2

	9、在最后执行下一个宏任务队列,即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. 结尾

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

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


console.log('start')

new Promise(function(resolve) {
  console.log('promise1')
  setTimeout(() => {
    console.log('timer1')
  },0)
  resolve()
}).then(() => {
  console.log('promise2')
  setTimeout(() => {
    console.log('timer2')
  },0)
})

const promise1 = Promise.resolve().then(() => {
    console.log('promise3')
    setTimeout(() => {
        console.log('timer3')
    },0)
})

async function async1() {
  console.log('async1')
  await async2()
  console.log('async1 end');
}

async function async2 () {
  console.log('async2')
}

async1()

setTimeout(() => {
  console.log('timer4')
},0)

console.log('script end')

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【异步系列五】关于async/await与promise执行顺序详细解析及原理详解 的相关文章

  • 这段代码有什么问题。如果用户选择或不选择复选框,为什么它仍然显示 MsgBox? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 无论我是否选择复选框 它仍然会给出
  • Jquery文件上传插件进度条

    这个插件 https github com blueimp jQuery File Upload wiki管理网页中的文件上传 并且可以在上传过程中添加很多 UI 元素 您创建一个输入文件类型元素 然后绑定 js 文件 使用实例化代码和 w
  • 在 Node.js 中包含另一个文件中的 JavaScript 类定义

    我正在为 Node js 编写一个简单的服务器 并且使用我自己的类 名为User看起来像 function User socket this socket socket this nickname null just the typical
  • mocha.opts 已弃用,如何迁移到 package.json?

    我正在开发一个大型项目 自从上周我更新了摩卡以来 现在我们收到警告 DeprecationWarning 通过 mocha opts 进行的配置已被弃用并且 将从 Mocha 的未来版本中删除 使用 RC 文件或 改为 package js
  • ES6 类文字中的 IIFE

    在 ES5 中我们都可以这样做 myClass prototype myMethod function return function 我可以对 ES6 类文字执行同样的操作吗 不 至少现在还没有 ES6 类仅支持声明方法 因此任何不直接为
  • jQuery数据表设置列设计和成功回调中的值

    我为我的数据表编写了以下代码 它用我的数据库中的内容填充表 如下所示 if datatable null datatable destroy datatable tableProducts DataTable pageLength 50 b
  • 如何从对象数组中删除所有重复项?

    This is a large array of objects e g let totalArray id rec01dTDP9T4ZtHL4 fields user id 170180717 user name abcdefg even
  • 从 ES6 模块导入函数表达式或函数声明有什么区别?

    据我了解 参见第 16 3 2 1 节 http exploringjs com es6 ch modules html ES6 允许函数 类导出操作数使用不同的语法 区别在于导出的函数是否需要在导入时解释为函数声明 在这种情况下 您可以编
  • 光标:IE 8 和 9 中的自动行为

    我想要的是为整个正文标记指定cursor pointer 这样页面的背景是可点击的 但我也希望页面的其余部分像以前一样工作 所以我尝试为div设置cursor auto 其中包含这一页 在 FF Chrome 和 safari 中 它工作得
  • “move(-1)”作为 AngularJS 表达式有什么问题吗?

    我收到此错误 parse ueoe Unexpected end of expression move 从这段代码来看
  • 在each() 和forEach() 中使用break 和 continue

    如果我们不能使用 break 和 continue 关键字 我不确定我是否理解函数式循环 映射的价值 我可以做这个 collections users models forEach function item index can t use
  • Google 地图 Javascript v3 折线点击事件

    我正在尝试显示一张地图 其中有多条路线布置为折线 单击多段线时 我想显示特定于该线的数据 将数据与线关联不是问题 但无论单击哪条线 显示的数据都会与最近绘制的线关联 就好像每条新折线都会覆盖最后一条线一样 我有一个数据库 其中包含 gpx
  • JavaScript/jQuery - “$ 未定义 - $function()”错误

    我正在尝试运行 JavaScript jQuery 函数并且Firebug http en wikipedia org wiki Firebug 28software 29得到错误 is not defined function JavaS
  • 带有桌子的嵌套表

    我在应用了表排序器的表中嵌套了表 它在嵌套表中添加了排序标题 但是它们没有对行进行排序 并且抛出了JavaScript错误 我想拥有 嵌套表不可排序 巢表上的排序实际上可以工作 但不是现状 您的第一个选择要容易得多 使嵌套表不可排序 像这样
  • 如何处理 setTimeout() 的多个实例?

    阻止创建 setTimeout 函数的多个实例 在 JavaScript 中 的最推荐 最佳方法是什么 一个例子 伪代码 function mouseClick moveDiv div 0001 mouseX mouseY function
  • 如何在react.js中将/n替换为换行符?

    我正在尝试更换每一个 n to a br tag in ReactJS In my note note对象有一个包含多个的字符串 n in it 示例注释 注释 test ntest ntest 我尝试过的ReactJS note note
  • javascript 闭包和对象引用

    我的情况有点晦涩难懂 主要是因为我认为我已经掌握了闭包 所以基本上我想要的是将集合重置为默认值 假设我有一个集合 它具有带有对象参数数组的构造函数 var c new collection x y z 然后集合定期更新 因为我没有保留数组的
  • “memset”没有 DLL 那么如何 ctype 它

    如何使用memset在 jsc 类型中 没有对应的 DLL 我搜索 搜索了 js ctype 代码 但找不到要破解的示例 如果你只是想memset一个数组为零字节 然后我有 好消息 大家 js ctypes 会将新数组初始化为零 否则 最简
  • 如何使用 Chart.js 版本 3.2.1 在圆环图中添加文本

    我正在使用 Canvas 在 HTML 中使用 如何使用在圆环图中添加文本 这是我的 javascript 代码和 HTML 代码 我使用了图表js版本3 2 1 所以请给出相同版本 3 的解决方案 var overallStatsCanv
  • 用于替换前 5 个数字的正则表达式,无论它们之间有什么?

    我正在努力实现以下匹配 Input 123 45 6789 123456789 1234 正则表达式尝试输出 d 5 123 45 6789 123456789 1234 d 2 3 123 45 6789 123456789 1234 d

随机推荐

  • 计算机采用二进制每秒,计算机为什么采用二进制

    计算机为什么采用二进制 2018 09 12 电脑为什么要采用二进制计算 计算机中的一切计算都是用二进制进行的 平时我们用的十进制是逢十进一 二进制则是逢二进一 我们用的算盘事实上有两种用法 一种是十进制 一种是十六进制 算盘中代表 五 的
  • 嵌入式Linux下用C语言写后端接口——CGI实现

    文章目录 简介 实验环境 下载CGIC库源码 配置CGIC编译 测试CGI接口 编写一个简单的获取表单的CGI接口 测试login cgi CGIC接口API 简介 CGI Common Gateway Interface 公共网关接口 是
  • Python更改文件的编码格式

    Python更改文件的编码格式 import os from chardet universaldetector import UniversalDetector def change encode file change 2 type d
  • MySQL Flashback 闪回功能详解

    1 简介 mysqlbinlog flashback 闪回 用于快速恢复由于误操作丢失的数据 在DBA误操作时 可以把数据库恢复到以前某个时间点 或者说某个binlog的某个pos 比如忘了带where条件的update delete操作
  • FreeType简介及在vs2010的编译使用

    FreeType库是一个开源 高质量 可扩展 可定制 可移植的字体引擎 它提供统一的接口来访问多种字体格式文件 包括点阵字 TrueType OpenType Type1 CID CFF Windows FON FNT X11 PCF等 F
  • 2021.11.30 面试题

    1 请你介绍一下map的分类和常见的情况 java为数据结构中的映射定义了一个接口java util Map 它有四个实现类 分别是HashMap Hashtable LinkedHashMap 和TreeMap Map主要用于存储健值对
  • simulink仿真 adc 采样ePWM输出例程

    新建文件夹并用matlab打开 写入这两个模块 配置 ADC 配置ePWM 不使能B 关了就行 其他的默认即可 配置烧录 连线 示波器接pwma1 和地 adc chanl1接 3 3v或者 0 3 3 都行 转化是 x 3 3 2 12
  • backtrace函数与assert断言宏封装

    这篇文章是在阅读 sylar 框架时 对断言宏的封装所做的总结 在实际开发中 我们经常会遇到一种境况 如果程序执行的不是我们想要的正确结果 需要程序立即中断执行 我们希望得到其有效的错误信息 比如其出现错误的函数 文件 代码行号 和参数文本
  • iOS App icon、启动页、图标规范

    原文 iOS App icon 启动页 图标规范 以下内容都是我在做App时通过自己的经验和精品的分析得来的 希望会帮助到你 但是有时个别情况也要个别分析 要活学活用 一 App Icon 在设计iOS App Icon时 设计师不需要切圆
  • 招募 AIGC 训练营助教 @上海

    诚挚邀请对社区活动感兴趣的你 成为我们近期开展的训练营助教 与我们共同开启这场创新之旅 助教需要参与 协助策划和组织训练营活动 协助招募和筛选学员 协助制定训练营的宣传方案 负责协调和组织各项活动 助教可获得 AIGC知识库 获得社区提供的
  • 服务器端Session、客户端Session和Cookie的区别

    1 Session其实分为客户端Session和服务器端Session 当用户首次与Web服务器建立连接的时候 服务器会给用户分发一个 SessionID作为标识 SessionID是一个由24个字符组成的随机字符串 用户每次提交页面 浏览
  • 微型小程序页面跳转加携带数据

    一 WXML中
  • 列表数据转树形数据 trees-plus 使用方法(支持typescript)

    trees plus Operations related to tree data Install npm i trees plus S Import import TreesPlus from trees plus Usage impo
  • 如何使用DLL函数动态加载-静态加载

    公司里的项目里用到加密解密 使用的是客户指定的DLL库来加密解密 开始 我按照以前的方法来使用DLL库 这里也介绍下吧 虽然网上很多 一般动态加载DLL的步骤如下 HINSTANCE DLL库实例名 LoadLibrary T DLL库名
  • 高德api 实现根据中文地址地图打点弹窗

  • diffusion models笔记

    ELBO of VDM Understanding 1 中讲 variational diffusion models VDM 的 evidence lower bound ELBO 推导时 53 式有一个容易引起误会的记号
  • Promethus(普罗米修斯)安装与配置(亲测可用)

    1 普罗米修斯概述 Prometheus 是由go语言 golang 开发 是一套开源的监控 报警 时间序列数 据库的组合 适合监控docker容器 Prometheus是最初在SoundCloud上构建的开源系统监视和警报工具包 自201
  • 字符串匹配算法0-基本概念

    字符串匹配的算法在很多领域都有重要的应用 这就不多说了 我们考虑一下算法的基本的描述 给定大小为 字母表 上的长度为n的文本t和长度为m的模式p 找出t中所有的p的出现的地方 一个长度为m的串p表示为一个数组p 0 m 1 这里m 0 当然
  • [前端系列第5弹]JQuery简明教程:轻松掌握Web页面的动态效果

    在这篇文章中 我将介绍jQuery的基本概念 语法 选择器 方法 事件和插件 以及如何使用它们来实现Web页面的动态效果 还将给一些简单而实用的例子 让你可以跟着我一步一步地编写自己的jQuery代码 一 什么是JQuery JQuery是
  • 【异步系列五】关于async/await与promise执行顺序详细解析及原理详解

    前段时间总结了几篇关于异步原理 Promise原理 Promise面试题 async await 原理的文章 链接如下 感兴趣的可以去看下 相信会有所收获 一篇文章理清JavaScript中的异步操作原理 Promise原理及执行顺序详解