如何在 JS 循环中正确使用 async 与 await

论坛 期权论坛 期权     
筑梦编程   2019-6-16 05:39   3659   0

引言

async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案。目前,async / await这个特性已经是stage 3的建议
然而,由于部分开发人员对该语法糖原理的认识不够清晰,泛滥而不加考虑地随意使用async/await ,可能会我们陷入了新的麻烦之中。
概述
下面是随处可见的现代化前端代码:
  1. [/code][list=1][*](async () => {
  2. [*]
  3. [*] const pizzaData = await getPizzaData(); // async call
  4. [*]
  5. [*] const drinkData = await getDrinkData(); // async call
  6. [*]
  7. [*] const chosenPizza = choosePizza(); // sync call
  8. [*]
  9. [*] const chosenDrink = chooseDrink(); // sync call
  10. [*]
  11. [*] await addPizzaToCart(chosenPizza); // async call
  12. [*]
  13. [*] await addDrinkToCart(chosenDrink); // async call
  14. [*]
  15. [*] orderItems(); // async call
  16. [*]
  17. [*]})();
  18. [/list]await 语法本身没有问题,有时候可能是使用者用错了。当 pizzaData 与 drinkData 之间没有依赖时,顺序的 await 会最多让执行时间增加一倍的 getPizzaData 函数时间,因为 getPizzaData 与 getDrinkData应该并行执行。
  19. 回到我们吐槽的回调地狱,虽然代码比较丑,带起码两行回调代码并不会带来阻塞。
  20. 看来语法的简化,带来了性能问题,而且直接影响到用户体验,是不是值得我们反思一下?
  21. 正确的做法应该是先同时执行函数,再 await 返回值,这样可以并行执行异步函数:
  22. [code]
复制代码
  • (async () => {

  • const pizzaPromise = selectPizza();

  • const drinkPromise = selectDrink();

  • await pizzaPromise;

  • await drinkPromise;

  • orderItems(); // async call

  • })();
或者使用
  1. Promise.all
复制代码
可以让代码更可读:
  1. [/code][list=1][*](async () => {
  2. [*]
  3. [*] Promise.all([selectPizza(), selectDrink()]).then(orderItems); // async call
  4. [*]
  5. [*]})();
  6. [/list]看来不要随意的 await,它很可能让你代码性能降低。
  7. 精读
  8. 仔细思考为什么 async/await 会被滥用,笔者认为是它的功能比较反直觉导致的。
  9. 首先 async/await 真的是语法糖,功能也仅是让代码写的舒服一些。先不看它的语法或者特性,仅从语法糖三个字,就能看出它一定是局限了某些能力。
  10. 举个例子,我们利用 html 标签封装了一个组件,带来了便利性的同时,其功能一定是 html 的子集。又比如,某个轮子哥觉得某个组件 api 太复杂,于是基于它封装了一个语法糖,我们多半可以认为这个便捷性是牺牲了部分功能换来的。
  11. 功能完整度与使用便利度一直是相互博弈的,很多框架思想的不同开源版本,几乎都是把功能完整度与便利度按照不同比例混合的结果。
  12. 那么回到 async/await 它的解决的问题是回调地狱带来的灾难:
  13. [code]
复制代码
  • a(() => {

  • b(() => {

  •    c();

  • });

  • });
为了减少嵌套结构太多对大脑造成的冲击,async/await 决定这么写:
  1. [/code][list=1][*]await a();
  2. [*]
  3. [*]await b();
  4. [*]
  5. [*]await c();
  6. [/list]虽然层级上一致了,但逻辑上还是嵌套关系,这不是另一个程度上增加了大脑负担吗?而且这个转换还是隐形的,所以许多时候,我们倾向于忽略它,所以造成了语法糖的滥用。
  7. 理解语法糖
  8. 虽然要正确理解 async/await 的真实效果比较反人类,但为了清爽的代码结构,以及防止写出低性能的代码,还是挺有必要认真理解 async/await 带来的改变。
  9. 首先 async/await 只能实现一部分回调支持的功能,也就是仅能方便应对层层嵌套的场景。其他场景,就要动一些脑子了。
  10. 比如两对回调:
  11. [code]
复制代码
  • a(() => {
  • b();
  • });

  • c(() => {
  • d();
  • });
如果写成下面的方式,虽然一定能保证功能一致,但变成了最低效的执行方式:
  1. [/code][list=1][*]await a();
  2. [*]
  3. [*]await b();
  4. [*]
  5. [*]await c();
  6. [*]
  7. [*]await d();
  8. [/list]因为翻译成回调,就变成了:
  9. [code]
复制代码
  • a(() => {
  • b(() => {
  •    c(() => {
  •      d();
  •    });
  • });
  • });
然而我们发现,原始代码中,函数
  1. c
复制代码
可以与
  1. a
复制代码
同时执行,但 async/await 语法会让我们倾向于在
  1. b
复制代码
执行完后,再执行
  1. c
复制代码

所以当我们意识到这一点,可以优化一下性能:
  1. [/code][list=1][*]const resA = a();
  2. [*]const resC = c();
  3. [*]
  4. [*]await resA;
  5. [*]b();
  6. [*]
  7. [*]await resC;
  8. [*]d();
  9. [/list]但其实这个逻辑也无法达到回调的效果,虽然 [code]a
复制代码
  1. c
复制代码
同时执行了,但
  1. d
复制代码
原本只要等待
  1. c
复制代码
执行完,现在如果
  1. a
复制代码
执行时间比
  1. c
复制代码
长,就变成了:
  1. [/code][list=1][*]a(() => {
  2. [*]
  3. [*] d();
  4. [*]
  5. [*]});
  6. [/list]看来只有完全隔离成两个函数:
  7. [code]
复制代码
  • (async () => {
  • await a();
  • b();
  • })();

  • (async () => {
  • await c();
  • d();
  • })();
或者利用
  1. Promise.all
复制代码
:
  1. [/code][list=1][*]async function ab() {
  2. [*] await a();
  3. [*] b();
  4. [*]}
  5. [*]
  6. [*]async function cd() {
  7. [*] await c();
  8. [*] d();
  9. [*]}
  10. [*]
  11. [*]Promise.all([ab(), cd()]);
  12. [/list]这就是我想表达的可怕之处。回调方式这么简单的过程式代码,换成 async/await 居然写完还要反思一下,再反推着去优化性能,这简直比回调地狱还要可怕。
  13. 而且大部分场景代码是非常复杂的,同步与 await 混杂在一起,想捋清楚其中的脉络,并正确优化性能往往是很困难的。但是我们为什么要自己挖坑再填坑呢?很多时候还会导致忘了填。
  14. 原文作者给出了 [code]Promise.all
复制代码
的方式简化逻辑,但笔者认为,不要一昧追求 async/await 语法,在必要情况下适当使用回调,是可以增加代码可读性的。
总结

async/await 回调地狱提醒着我们,不要过渡依赖新特性,否则可能带来的代码执行效率的下降,进而影响到用户体验。同时,笔者认为,也不要过渡利用新特性修复新特性带来的问题,这样反而导致代码可读性下降。

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。




公众号ID:tzbc666扫码关注最新动态点个好看和转发也是一种支持哟!
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:75
帖子:15
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP