你不知道的 async、await 魔鬼细节

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

作者:Squirrel_

https://juejin.cn/post/7194744938276323384

0、前言

关于promise、async/await的使用相信很多小伙伴都比较熟悉了,但是提到事件循环机制输出结果类似的题目,你敢说都会?

试一试?

🌰1:

async function async1 () {await new Promise((resolve, reject) => {resolve()})console.log('A')
}async1()new Promise((resolve) => {console.log('B')resolve()
}).then(() => {console.log('C')
}).then(() => {console.log('D')
})// 最终结果👉: B A C D

🌰2:

async function async1 () {await async2()console.log('A')
}async function async2 () {return new Promise((resolve, reject) => {resolve()})
}async1()new Promise((resolve) => {console.log('B')resolve()
}).then(() => {console.log('C')
}).then(() => {console.log('D')
})// 最终结果👉: B C D A

❓基本一样的代码为什么会出现差别,话不多说👇

1、async 函数返回值

在讨论 await 之前,先聊一下 async 函数处理返回值的问题,它会像 Promise.prototype.then 一样,会对返回值的类型进行辨识。

👉根据返回值的类型,引起 js引擎 对返回值处理方式的不同

📑结论:async函数在抛出返回值时,会根据返回值类型开启不同数目的微任务

  • return结果值:非thenable、非promise(不等待)

  • return结果值:thenable(等待 1个then的时间)

  • return结果值:promise(等待 2个then的时间)

🌰1:

async function testA () {return 1;
}testA().then(() => console.log(1));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));// (不等待)最终结果👉: 1 2 3

🌰2:

async function testB () {return {then (cb) {cb();}};
}testB().then(() => console.log(1));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));// (等待一个then)最终结果👉: 2 1 3

🌰3:

async function testC () {return new Promise((resolve, reject) => {resolve()})
}testC().then(() => console.log(1));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));// (等待两个then)最终结果👉: 2 3 1async function testC () {return new Promise((resolve, reject) => {resolve()})
} testC().then(() => console.log(1));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3)).then(() => console.log(4))// (等待两个then)最终结果👉: 2 3 1 4

看了这三个🌰是不是对上面的结论有了更深的认识?

稍安勿躁,来试试一个经典面试题👇

async function async1 () {console.log('1')await async2()console.log('AAA')
}async function async2 () {console.log('3')return new Promise((resolve, reject) => {resolve()console.log('4')})
}console.log('5')setTimeout(() => {console.log('6')
}, 0);async1()new Promise((resolve) => {console.log('7')resolve()
}).then(() => {console.log('8')
}).then(() => {console.log('9')
}).then(() => {console.log('10')
})
console.log('11')// 最终结果👉: 5 1 3 4 7 11 8 9 AAA 10 6

👀做错了吧?

哈哈没关系

步骤拆分👇:

  1. 先执行同步代码,输出5

  2. 执行setTimeout,是放入宏任务异步队列中

  3. 接着执行async1函数,输出1

  4. 执行async2函数,输出3

  5. Promise构造器中代码属于同步代码,输出4

    async2函数的返回值是Promise,等待2then后放行,所以AAA暂时无法输出

  6. async1函数暂时结束,继续往下走,输出7

  7. 同步代码,输出11

  8. 执行第一个then,输出8

  9. 执行第二个then,输出9

  10. 终于到了两个then执行完毕,执行async1函数里面剩下的,输出AAA

  11. 再执行最后一个微任务then,输出10

  12. 执行最后的宏任务setTimeout,输出6

❓是不是豁然开朗,欢迎点赞收藏!

2、await 右值类型区别

2.1、非 thenable

🌰1:

async function test () {console.log(1);await 1;console.log(2);
}test();
console.log(3);
// 最终结果👉: 1 3 2

🌰2:

function func () {console.log(2);
}async function test () {console.log(1);await func();console.log(3);
}test();
console.log(4);// 最终结果👉: 1 2 4 3

🌰3:

async function test () {console.log(1);await 123console.log(2);
}test();
console.log(3);Promise.resolve().then(() => console.log(4)).then(() => console.log(5)).then(() => console.log(6)).then(() => console.log(7));// 最终结果👉: 1 3 2 4 5 6 7

Note:

await后面接非 thenable 类型,会立即向微任务队列添加一个微任务then但不需等待

2.2、thenable类型

async function test () {console.log(1);await {then (cb) {cb();},};console.log(2);
}test();
console.log(3);Promise.resolve().then(() => console.log(4)).then(() => console.log(5)).then(() => console.log(6)).then(() => console.log(7));// 最终结果👉: 1 3 4 2 5 6 7

Note:

await 后面接 thenable 类型,需要等待一个 then 的时间之后执行

2.3、Promise类型

async function test () {console.log(1);await new Promise((resolve, reject) => {resolve()})console.log(2);
}test();
console.log(3);Promise.resolve().then(() => console.log(4)).then(() => console.log(5)).then(() => console.log(6)).then(() => console.log(7));// 最终结果👉: 1 3 2 4 5 6 7

❓为什么表现的和非 thenable 值一样呢?为什么不等待两个 then 的时间呢?

Note:

  • TC 39(ECMAScript标准制定者) 对await 后面是 promise 的情况如何处理进行了一次修改,移除了额外的两个微任务,在早期版本,依然会等待两个 then 的时间

  • 有大佬翻译了官方解释:更快的 async 函数和 promises[1],但在这次更新中并没有修改 thenable 的情况


这样做可以极大的优化 await 等待的速度👇

async function func () {console.log(1);await 1;console.log(2);await 2;console.log(3);await 3;console.log(4);
}async function test () {console.log(5);await func();console.log(6);
}test();
console.log(7);Promise.resolve().then(() => console.log(8)).then(() => console.log(9)).then(() => console.log(10)).then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11

Note:

awaitPromise.prototype.then 虽然很多时候可以在时间顺序上能等效,但是它们之间有本质的区别

  • test 函数中的 await 会等待 func 函数中所有的 await 取得 恢复函数执行 的命令并且整个函数执行完毕后才能获得取得 恢复函数执行的命令;

  • 也就是说,func 函数的 await 此时不能在时间的顺序上等效 then,而要等待到 test 函数完全执行完毕;

  • 比如这里的数字6很晚才输出,如果单纯看成then的话,在下一个微任务队列执行时6就应该作为同步代码输出了才对。


所以我们可以合并两个函数的代码👇

async function test () {console.log(5);console.log(1);await 1;console.log(2);await 2;console.log(3);await 3;console.log(4);await null;console.log(6);
}test();
console.log(7);Promise.resolve().then(() => console.log(8)).then(() => console.log(9)).then(() => console.log(10)).then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11

因为将原本的函数融合,此时的 await 可以等效为 Promise.prototype.then,又完全可以等效如下代码👇

async function test () {console.log(5);console.log(1);Promise.resolve().then(() => console.log(2)).then(() => console.log(3)).then(() => console.log(4)).then(() => console.log(6))
}test();
console.log(7);Promise.resolve().then(() => console.log(8)).then(() => console.log(9)).then(() => console.log(10)).then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11

以上三种写法在时间的顺序上完全等效,所以你 完全可以将 await 后面的代码可以看做在 then 里面执行的结果,又因为 async 函数会返回 promise 实例,所以还可以等效成👇

async function test () {console.log(5);console.log(1);
}test().then(() => console.log(2)).then(() => console.log(3)).then(() => console.log(4)).then(() => console.log(6))console.log(7);Promise.resolve().then(() => console.log(8)).then(() => console.log(9)).then(() => console.log(10)).then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11

可以发现,test 函数全是走的同步代码...

所以👉:**async/await 是用同步的方式,执行异步操作**

3、🌰

🌰1:

async function async2 () {new Promise((resolve, reject) => {resolve()})
}async function async3 () {return new Promise((resolve, reject) => {resolve()})
}async function async1 () {// 方式一:最终结果:B A C D// await new Promise((resolve, reject) => {//     resolve()// })// 方式二:最终结果:B A C D// await async2()// 方式三:最终结果:B C D Aawait async3()console.log('A')
}async1()new Promise((resolve) => {console.log('B')resolve()
}).then(() => {console.log('C')
}).then(() => {console.log('D')
})

大致思路👇:

  • 首先,**async函数的整体返回值永远都是Promise,无论值本身是什么**

  • 方式一:await的是Promise,无需等待

  • 方式二:await的是async函数,但是该函数的返回值本身是**非thenable**,无需等待

  • 方式三:await的是async函数,且返回值本身是Promise,需等待两个then时间

🌰2:

function func () {console.log(2);// 方式一:1 2 4  5 3 6 7// Promise.resolve()//     .then(() => console.log(5))//     .then(() => console.log(6))//     .then(() => console.log(7))// 方式二:1 2 4  5 6 7 3return Promise.resolve().then(() => console.log(5)).then(() => console.log(6)).then(() => console.log(7))
}async function test () {console.log(1);await func();console.log(3);
}test();
console.log(4);

步骤拆分👇:

  • 方式一:

    • 同步代码输出1、2,接着将log(5)处的then1加入微任务队列,await拿到确切的func函数返回值undefined,将后续代码放入微任务队列(then2,可以这样理解)

    • 执行同步代码输出4,到此,所有同步代码完毕

    • 执行第一个放入的微任务then1输出5,产生log(6)的微任务then3

    • 执行第二个放入的微任务then2输出3

    • 然后执行微任务then3,输出6,产生log(7)的微任务then4

    • 执行then4,输出7

  • 方式二:

    • 同步代码输出1、2await拿到func函数返回值,但是并未获得具体的结果(由Promise本身机制决定),暂停执行当前async函数内的代码(跳出、让行)

    • 输出4,到此,所有同步代码完毕

    • await一直等到Promise.resolve().then...执行完成,再放行输出3

方式二没太明白❓

继续👇

function func () {console.log(2);return Promise.resolve().then(() => console.log(5)).then(() => console.log(6)).then(() => console.log(7))
}async function test () {console.log(1);await func()console.log(3);
}test();
console.log(4);new Promise((resolve) => {console.log('B')resolve()
}).then(() => {console.log('C')
}).then(() => {console.log('D')
})// 最终结果👉: 1 2 4    B 5 C 6 D 7 3

还是没懂?

继续👇

async function test () {console.log(1);await Promise.resolve().then(() => console.log(5)).then(() => console.log(6)).then(() => console.log(7))console.log(3);
}test();
console.log(4);new Promise((resolve) => {console.log('B')resolve()
}).then(() => {console.log('C')
}).then(() => {console.log('D')
})// 最终结果👉: 1 4    B 5 C 6 D 7 3

Note:

综上,await一定要等到右侧的表达式有确切的值才会放行,否则将一直等待(阻塞当前async函数内的后续代码),不服看看这个👇

  • function func () {return new Promise((resolve) => {console.log('B')// resolve() 故意一直保持pending})
    }async function test () {console.log(1);await func()console.log(3);
    }test();
    console.log(4);
    // 最终结果👉: 1 B 4 (永远不会打印3)// ---------------------或者写为👇-------------------
    async function test () {console.log(1);await new Promise((resolve) => {console.log('B')// resolve() 故意一直保持pending})console.log(3);
    }test();
    console.log(4);
    // 最终结果👉: 1 B 4 (永远不会打印3)

🌰3:

async function func () {console.log(2);return {then (cb) {cb()}}
}async function test () {console.log(1);await func();console.log(3);
}test();
console.log(4);new Promise((resolve) => {console.log('B')resolve()
}).then(() => {console.log('C')
}).then(() => {console.log('D')
})// 最终结果👉: 1 2 4 B C 3 D

步骤拆分👇:

  • 同步代码输出1、2

  • await拿到func函数的具体返回值thenable,将当前async函数内的后续代码放入微任务then1(但是需要等待一个then时间)

  • 同步代码输出4、B,产生log(C)的微任务then2

  • 由于then1滞后一个then时间,直接执行then2输出C,产生log(D)的微任务then3

  • 执行原本滞后一个then时间的微任务then1,输出3

  • 执行最后一个微任务then3输出D

4、总结

async函数返回值

  • 📑结论:async函数在抛出返回值时,会根据返回值类型开启不同数目的微任务

    • return结果值:非thenable、非promise(不等待)

    • return结果值:thenable(等待 1个then的时间)

    • return结果值:promise(等待 2个then的时间)

await右值类型区别

  • 接非 thenable 类型,会立即向微任务队列添加一个微任务then但不需等待

  • thenable 类型,需要等待一个 then 的时间之后执行

  • Promise类型(有确定的返回值),会立即向微任务队列添加一个微任务then但不需等待

    • TC 39 对await 后面是 promise 的情况如何处理进行了一次修改,移除了额外的两个微任务,在早期版本,依然会等待两个 then 的时

43d3f4cd6211fb1937a537328c6c771d.png

往期推荐

GPT-4发布!ChatGPT大升级!太太太太强了!

5de7a2681517a8f668c31cfd8ddc69f9.png

字节跳动自研 Web 构建工具 Rspack 正式发布

0f79b08c3c7ecc8f493e37676d8c7a28.png

速来!腾讯微信团队招人,简历直推面试官!

4673a1d34541c5a103bafb739800c42e.png


最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

aad50a8a65bb618e0914db35d1e8cb5f.jpeg

d10ff4935c4fb18e3a3f0e9027363767.png

点个在看支持我吧

93410a7cd9cb9df3bd3ab41239a2ff61.gif

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

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

相关文章

我与ChatGPT又聊了聊:什么是真正的云原生大数据平台

图片来源 | 文心一格 小智:传统大数据平台是什么样的?企业使用传统大数据平台有哪些弊端? 小智:云原生为什么这么火?企业如何借助云原生实现数据驱动? 小智:你听过在Kubernetes上部署的容器化云…

【云原生】我将ChatGPT变成Kubernetes 和Helm 终端

{kubectl get po,deploy,svc}{kubectl run --imagenginx nginx-app --port80 --env“DOMAINcluster”}{kubectl expose deployment nginx-app --port80 --namenginx-http}{kubectl get po,svc,deploy}{curl 10.100.67.94:80}{helm…

关于云原生,我问了 ChatGPT 几个问题......

2 个月用户破亿,一举超过 Tik Tok 成为史上增速最快的消费级应用程序,ChatGPT 的诞生给沉寂的科技圈丢下了一块巨大的石头。这场生成式 AI 掀起的浪潮,让人不禁重回到当年人类智慧的大溃败——AlphaGo 战胜李世石,震撼依旧但其背后…

教你接入GPT4,不用梯子也能玩

介绍 chatgpt最近十分火爆,但大多少开发接入的都是gpt3.5,今天教教大家如何快速接入gpt4 使用 接入很简单,需要去API文档获取你的token填入,每个账号都有白嫖次数,以下是node代码 const { data } await axios({url…

GPT:你知道这五年我怎么过的么?

时间轴 GPT 首先最初版的GPT,来源于论文Improving Language Understanding by Generative Pre-Training(翻译过来就是:使用通用的预训练来提升语言的理解能力)。GPT这个名字其实并没有在论文中提到过,后人将论文名最后…

【2023.5.3~2023.5.9】CTF刷题记录

目录 日期:2023.5.3 题目:[GWCTF 2019]pyre 日期:2023.5.4 题目:[ACTF新生赛2020]easyre 题目:DASCTF Apr.2023 X SU战队2023开局之战 【简单】easyRE 日期:2023.5.5 题目:findit 题目&…

浅尝Transformer和LLM

文章目录 TransformerTransformer的衍生BERTPre-trainingBERT与其他方法的关系怎么用BERT做生成式任务? GPTPre-trainingFine-Tuning Transformer工具开源库特点 LLM系列推理服务 大语言模型势不可挡啊。 哲学上来说,语言就是我们的一切,语言…

【stable diffusion原理解读通俗易懂,史诗级万字爆肝长文,喂到你嘴里】

文章目录 一、前言(可跳过)二、stable diffusion1.clip2.diffusion modelforward diffusion (前向扩散)逆向扩散(reverse diffusion)采样图阶段小结 3.Unet modeltimestep_embedding采用正余弦编码 三、sta…

旋转的base,你见过吗wp

一、题目 前几天在ctfshow的qq交流群里看到有个师傅在问一道名为“旋转的base,你见过吗”的题目(但这道题不是ctfshow平台上的啦,后来听说好像是个比赛题),题目给出了一串编码过的字符串,但看题目名也能知…

OtterCTF—内存取证wp

目录 前言 一、工具说明 二、题目解析 1.What the password? 2.General Info 3.Play Time 4.Name Game 5.Name Game 2 6.Silly Rick 7.Hide And Seek 8.Path To Glory 9.Path To Glory 2 10.Bit 4 Bit 11.Graphics For The Weak 12.Recovery 13.Closure 总结 前言 前几天有幸…

电商打工人的饭碗,AIGC还端不走

文 | 螳螂观察 作者 | 鲸胖胖 以ChatGPT、Midjourney、文心一言等为代表的AIGC产品,已经在全球掀起新一轮的AI技术变革新浪潮,再度刷新了人们对AI的认知,多个行业的商业模式和生态必然在未来会被彻底重构。 前不久,36氪就测使用…

巴比特 | 元宇宙每日必读:用虹膜信息换基本收入?OpenAI创始人顶着质疑声为其Web3项目Worldcoin再寻1亿美元融资...

摘要:据元宇宙日爆报道,OpenAl的CEO要为他两年前创办的币圈项目worldcoin再寻1亿美元融资,该项目于5月8日面向全球推出加密钱包WorldApp,要给“无条件为全民空投代币”,此外,项目方还为这款钱包的推出发行了…

类 ChatGPT 开源软件,开发者用的上吗?

声明:本文是 Preethi Cheguri 所著文章《ChatGPT Equivalent Is Open-Source, But it Is of No Use to Developers》的中文译文。 原文链接:https://www.analyticsinsight.net/chatgpt-equivalent-is-open-source-but-it-is-of-no-use-to-developers/ 类…

【原创】运维工程师涨薪计划,chatGPT帮你做规划

文章目录 1、运维工程师怎么涨薪呢?a)加大深度b)加大广度 2、运维工程师何处去呢?3、chatGPT告诉你3年、5年、10年运维和开发的现状;有运维经验的工程师,搞开发好吗薪资会有显著提升吗以数据证明&#xff0…

计算机视觉实战--OpenCV进行红绿灯识别

前言: Hello大家好,我是Dream。 OpenCV是一个开源的计算机视觉库,可以用于实现各种图像和视频处理任务,包括红绿灯识别。可以帮助自动驾驶汽车、智能交通系统等设备准确地识别红绿灯的状态,以便做出正确的决策。今天&a…

时隔 3 年,全新 Linux QQ 正式开启公测!

出品 | OSC开源社区(ID:oschina2013) 2019 年,腾讯低调发布了 Linux QQ 的更新,目前版本停留在 2.0 Beta2。 时隔 3 年,QQ for Linux 基于 NT 技术架构迎来全新升级。今日(12 月 7 日)起&#x…

USG防火墙------内外网用户通过外网IP访问内部服务器(NAT)

实现需求:用户通过内外网用户通过公网IP访问内部服务器。 一、局域网配置:交换机(SW7)、防火墙(FW3)、服务器(Server1)、客户端(Client1) 二、配置思路 1、配置防火墙接口和IP地址…

趋势交易策略的买卖点选择,几种趋势介绍

这一篇来给大家说说顺势而为的趋势里面的交易策略。 清晰流畅的趋势都是比较难得的。那样的趋势一旦出现了,股价的运行 就会表现出一定的规律,即在上升趋势中表现出“更高的高点和更高的低点”,在下跌趋势中 表现出“更低的高点和更低的低点…

近期的热点风险事件都与这些内容相关

今天是母亲节,您辛苦了!愿妈妈们被岁月温柔以待图片 2022年青山伦镍事件,上演了一出《生死时速》大片,国际金融市场的猎杀、逼仓、巨亏等戏码,作为普通吃瓜群众可能只是当成饭后茶余的谈资,但其背后蕴藏着…

iBox系统源码分享,ibox的核心源码

iBox系统核心源码分享 from IBOX import IBOX_ART import json# https://etherscan.io/tx/0xbede5e44cc631303a22d066cc269f989469742b5bb6d9a74185e146dab9211e4 # https://mainnet.infura.io/v3/8a264f274fd94de48eb290d35db030ab # contract address is0x0632aDCab8F12edD3…