JavaScript 中 async/await 有没有必要彻底搞懂?

2024-04-25 18:18:18 +08:00
 shendaowu
花了两天时间把相关的东西看了个大概。然后感觉跟着例子照猫画虎应该没什么大问题了,但是自己独立写很可能会有问题。感觉理解得还不是很透彻。这种不透彻是否会导致出现什么严重的问题?我是主要看的这个: https://zh.javascript.info/async 。另外还看了一些其他相关的章节。
5786 次点击
所在节点    JavaScript
45 条回复
rioshikelong121
2024-04-25 23:15:24 +08:00
我曾经自认为搞的懂那么一点点过,,研究过 Task Queue, Micro Task Queue, 读过相关的 Spec, 看过各种相关的八股题目。读过 Promise/A+ 规范也照猫画虎实现过。
然而现在写了两年多的后端代码以后忘的差不多了。
非要我自己说,本质上 Promise 也好,Async/Await 也好,都只是下层提供的抽象,绝大部分情况下直接拿过来用可以满足大部分的场景的需要的。
花费大量精力搞懂这些的原因可能是:

1. 满足自己的好奇心
2. 通过理解一些偏底层的原理,在 1% 的场景下可以帮助你更快的定位 BUG / 问题从而解决问题,也就是为了解决抽象泄露才不得不去学习底层。
3. 可能也是最重要的:用来在面试的时候筛选人或者被我筛选。


现在让我来看的话,我自己可能对学这些知识兴趣不是那么大了,毕竟 AGI 都快实现了,学这些对于大部分的普通开发者来说性价比很低。
iqoo
2024-04-25 23:28:48 +08:00
完全有必要,代码可读性大幅提升
leokun
2024-04-25 23:36:46 +08:00
@favourstreet Promise 不是一个严格的 Monad ,不过挺像的
angrylid
2024-04-25 23:48:09 +08:00
不应付面试的话,JavaScript.info 真的足够应付业务开发的大多数问题了,遇到坑你再深入原理不迟。

赞成 17 楼所说。
RedNax
2024-04-26 03:11:36 +08:00
要深入研究也很简单,去 https://www.typescriptlang.org/play 写一点有 async/await 的 TS 代码,右边就有实时编译的 js 代码。
TS Config 的 Target 调整到 ES2016 (还没有 async/await ),就能看到 async/await 是怎么用 generator 实现的。
Target 调整到 ES5 (还没有 Generator ),就能看到 Generator 又是怎么用状态机实现的。
hukei
2024-04-26 08:37:40 +08:00
最近刚看 讲的最清楚 最简洁的教程视频 B 站虎哥 https://www.bilibili.com/video/BV1ig4y137Wr/
JavenXiao
2024-04-26 08:41:29 +08:00
做一些宏任务、微任务谁先打印出来的题,搞清楚 js 的事件循环
然后自己手搓一遍 Promise ,实现 Promise 的各种 api
然后努力理解一下 Generator 函数

最后组合起来,你就能理解 async/await 到底是个啥了
wjx0912
2024-04-26 08:47:51 +08:00
Moierby
2024-04-26 08:55:52 +08:00
精通了“茴”字的 4 种写法,也不影响大龄被裁员
waiaan
2024-04-26 09:02:24 +08:00
@shizhibuyu2023
这才是 async/await 的正解。
hazardous
2024-04-26 09:10:45 +08:00
会用就行,完全不需要了解底层。用熟了之后有兴趣就再细研究。
wolfan
2024-04-26 09:26:21 +08:00
正常开发不用,除非你是资深技术岗,需要对贵司的业务线优化和提高性能。
mioktiar56
2024-04-26 09:26:51 +08:00
当你问出这个问题的时候,答应其实已经出来了
hi2hi
2024-04-26 09:35:04 +08:00
一个没经历过`*function`的好运人
libook
2024-04-26 09:36:11 +08:00
10 年 JS 全栈经验,个人认为 async/await 是非常常用的东西,有必要掌握机制和原理。

属于 JS 异步机制,你可以通过了解 JS 事件循环、Promise 的原理来理解 async/await 。
wanguorui123
2024-04-26 10:17:59 +08:00
很简单 Promise = async function
forty
2024-04-26 11:23:38 +08:00
不太清楚有什么理解困难的。

无他,2 点:
1. 调用 async 异步函数的时候,如果你本意是有顺序要求的,一定要记得加 await ,否则顺序就不保证了,会出现非预期的效果。
2. 做好错误捕捉 try...catch.
F7TsdQL45E0jmoiG
2024-04-26 11:30:12 +08:00
但凡问“有没有必要”,就是没必要
agagega
2024-04-26 12:58:27 +08:00
计算机的知识从来都是可深可浅。其实所有领域的知识都是这样,但因为计算机整个体系人造属性特别强,所以这个特征表现得更明显。

对于 async/await ,最简单的理解:某些函数需要加 await 才能调用,用到 await 的函数需要用 async 标记,而调 async 函数也需要 await 。

更深一点:await 的其实是一个 Promise 对象,所有 async/await 的代码都可以改写为 Promise 和 then 。还有个叫 Generator 的东西,它的思路和 Promise 完全不一样。更古早的代码里还有种写法叫回调函数。这四者可以相互改写。

为什么要这样?因为一些操作(如网络请求、读写文件)需要等待取回数据,在等待过程中可以让 CPU 做其他的事情提高效率,等到数据读完再回来执行下一步代码,所以有了回调函数,这种写法叫做异步。NodeJS 里有同步读写文件的方法,可以和异步写法对比感受差异。

因为浏览器环境强调实时响应,所以 JS 一开始就很重视异步,这也是其他语言容易实现 sleep 函数而 JS 很麻烦的原因。

为什么有这么多种写法?程序运行的一个本质问题是,下一步要执行什么。通常的逻辑是,如果是普通语句就正常执行,如果是函数调用就传参进入,如果是 return 则回到上一层调用继续执行。

假如所有代码都写到一个 C 函数里,那我们不需要考虑这么多复杂的问题,因为我们可以自由地自己做调度。但我们希望在保证代码结构性(拆成函数)的同时,能够继续保证效率(在等待网络响应时能执行别的代码)。对于有匿名函数的语言,最直接的想法就是回调函数,而且通常这个回调函数除了固定的参数外,还要能从发请求的地方抓一些变量写东西(这被称作闭包)。回调函数的思路和处理网页事件是一致的。

但回调函数的写法意味着,每次要发送请求或者干什么事情时,都要在上一层回调函数里再新建一层回调函数,缩进层次会特别深,这被称作回调地狱( callback hell )。更麻烦的是,真实代码的逻辑可能不是线性的,可能有一个 if 或循环,里面执行一些异步代码,然后再出来。这种时候写回调函数简直就是脑力大作战。

换种思路,我们能不能发明一种特殊的函数,比传统函数更强大?传统函数一旦 return ,只能再从头开始执行。我们想要的这个新函数,可以从中间返回,下次再从上次返回的地方继续执行。这样我们就不用写回调了,发请求的时候返回,等响应到了再跳回来就可以了。JS 本身就有闭包,且支持函数套函数(参考计数器的写法),这种功能理论上只需要实现一个返回函数的函数就行了,但 JS 还是有一个专门的语法,叫生成器( generator )。

而回调函数的写法也有改进空间。如果能把异步操作封装进一个对象里,然后动态添加每一层的回调,虽然还得写很多匿名函数,但缩进上至少可以拍平了。这就是 Promise ,它用 then 函数连接下一个回调。

如果再把一层层的 then 里的函数也抽出来,那 then 就变成了 await 。而对于用到了 await 的函数,它的执行流程和生成器就非常像了,在 await 的地方跳出去交还给 runtime ,等到合适的时候再跳回来。所以用到 await 的函数和生成器生成的函数一样,不是普通函数,必须要有区别,所以有 async 标记。但到这里应该能理解,这几种写法是等价的,await 一个 async 函数,和 await 一个返回 Promise 的普通函数,是一回事。

关于回调函数的讨论还可以更进一步。回调函数的写法,是把事件响应后要执行的代码封装成一个函数。但如果我把所有要执行的语句都写成这个样子呢?比如正常的 a=b+1;b=c+1 ,把后一句也写在一个回调函数里面会怎么样?除了极度蛋疼外,你也会获得极度的灵活性。

实际上,这个由接下来的代码组成的回调函数有个学名,叫 continuation (有时中文翻译作续体)。一些语言(如 Lisp 、Ruby )直接提供了操作 continuation 对象的能力,但这个功能太强大,所以衍生出一种弱一些的变体,叫 delimited continuation ,也就是需要指定这个接下来执行的函数到哪为止。Kotlin 里面带 @ 符号的 return 就有点这个味。

而 async/await 的语法也可以进一步泛化。await 的逻辑是跳出去再跳回来,形式上是否和异常的 try catch 有点像?特别某些语言的异常还真的支持 retry 语句。还真的有种语法概念把 await 和异常统一起来,叫 algebraic affects 。所以 await 真的没什么神秘的。

到这一步其实还有很多问题没有深入,比如这些 runtime 在操作系统和原生代码层面是怎么实现和封装的,其他模型比如 go 的 goroutine 和传统的多进程模型等。但我想你现在理解 async/await 一定容易了很多:)
Leviathann
2024-04-26 14:12:38 +08:00
从两个方面了解把,一个顶层表达,一个底层实现

编程语言,首先是一门语言,有它想要表达的语义
但是抽象泄露是无法避免的,所以需要去了解底层实现,让你在用它表达的时候更有把握

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1035698

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX