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

论坛 期权论坛 期权     
前端桃园   2019-6-29 21:30   1853   0

阅读本文大约需要 9 分钟
  1. async
复制代码
  1. await
复制代码
的使用方式相对简单。 当你尝试在循环中使用
  1. await
复制代码
时,事情就会变得复杂一些。
在本文中,分享一些在如果循环中使用
  1. await
复制代码
值得注意的问题。
[h1]准备一个例子[/h1]对于这篇文章,假设你想从水果篮中获取水果的数量。
  1. const fruitBasket = { apple: 27, grape: 0, pear: 14};
复制代码
你想从
  1. fruitBasket
复制代码
获得每个水果的数量。 要获取水果的数量,可以使用
  1. getNumFruit
复制代码
函数。
  1. const getNumFruit = fruit => {  return fruitBasket[fruit];};const numApples = getNumFruit('apple');console.log(numApples); //27
复制代码
现在,假设
  1. fruitBasket
复制代码
是从服务器上获取,这里我们使用
  1. setTimeout
复制代码
来模拟。
  1. const sleep = ms => {  return new Promise(resolve => setTimeout(resolve, ms))};const getNumFruie = fruit => {  return sleep(1000).then(v => fruitBasket[fruit]);};getNumFruit("apple").then(num => console.log(num)); // 27
复制代码
最后,假设你想使用
  1. await
复制代码
  1. getNumFruit
复制代码
来获取异步函数中每个水果的数量。
  1. const control = async _ => {  console.log('Start')  const numApples = await getNumFruit('apple');  console.log(numApples);  const numGrapes = await getNumFruit('grape');  console.log(numGrapes);  const numPears = await getNumFruit('pear');  console.log(numPears);  console.log('End')}
复制代码

[h1]在 for 循环中使用 await[/h1]首先定义一个存放水果的数组:
  1. const fruitsToGet = [“apple”, “grape”, “pear”];
复制代码
循环遍历这个数组:
  1. const forLoop = async _ => {  console.log('Start');  for (let index = 0; index < fruitsToGet.length; index++) {    // 得到每个水果的数量  }  console.log('End')}
复制代码
  1. for
复制代码
循环中,过上使用
  1. getNumFruit
复制代码
来获取每个水果的数量,并将数量打印到控制台。
由于
  1. getNumFruit
复制代码
返回一个
  1. promise
复制代码
,我们使用
  1. await
复制代码
来等待结果的返回并打印它。
  1. const forLoop = async _ => {  console.log('start');  for (let index = 0; index < fruitsToGet.length; index ++) {    const fruit = fruitsToGet[index];    const numFruit = await getNumFruit(fruit);    console.log(numFruit);  }  console.log('End')}
复制代码
当使用
  1. await
复制代码
时,希望JavaScript暂停执行,直到等待 promise 返回处理结果。这意味着
  1. for
复制代码
循环中的
  1. await
复制代码
应该按顺序执行。
结果正如你所预料的那样。
  1. “Start”;“Apple: 27”;“Grape: 0”;“Pear: 14”;“End”;
复制代码

这种行为适用于大多数循环(比如
  1. while
复制代码
  1. for-of
复制代码
循环)…
但是它不能处理需要回调的循环,如
  1. forEach
复制代码
  1. map
复制代码
  1. filter
复制代码
  1. reduce
复制代码
。在接下来的几节中,我们将研究
  1. await
复制代码
如何影响
  1. forEach
复制代码
、map和
  1. filter
复制代码

[h1]在 forEach 循环中使用 await[/h1]首先,使用
  1. forEach
复制代码
对数组进行遍历。
  1. const forEach = _ => {  console.log('start');  fruitsToGet.forEach(fruit => {    //...  })  console.log('End')}
复制代码
接下来,我们将尝试使用
  1. getNumFruit
复制代码
获取水果数量。 (注意回调函数中的
  1. async
复制代码
关键字。我们需要这个
  1. async
复制代码
关键字,因为
  1. await
复制代码
在回调函数中)。
  1. const forEachLoop = _ => {  console.log('Start');  fruitsToGet.forEach(async fruit => {    const numFruit = await getNumFruit(fruit);    console.log(numFruit)  });  console.log('End')}
复制代码
我期望控制台打印以下内容:
  1. “Start”;“27”;“0”;“14”;“End”;
复制代码
但实际结果是不同的。在
  1. forEach
复制代码
循环中等待返回结果之前,JavaScrip先执行了 console.log('End')。
实际控制台打印如下:
  1. ‘Start’‘End’‘27’‘0’‘14’
复制代码

JavaScript 中的
  1. forEach
复制代码
不支持 promise 感知,也不支持
  1. async
复制代码
  1. await
复制代码
,所以不能在
  1. forEach
复制代码
使用
  1. await
复制代码

[h1]在 map 中使用 await[/h1]如果在
  1. map
复制代码
中使用
  1. await
复制代码
,
  1. map
复制代码
始终返回
  1. promise
复制代码
数组,这是因为异步函数总是返回
  1. promise
复制代码
  1. const mapLoop = async _ => {  console.log('Start')  const numFruits = await fruitsToGet.map(async fruit => {    const numFruit = await getNumFruit(fruit);    return numFruit;  })  console.log(numFruits);  console.log('End')}“Start”;“[Promise, Promise, Promise]”;“End”;
复制代码

如果你在
  1. map
复制代码
中使用
  1. await
复制代码
  1. map
复制代码
总是返回
  1. promises
复制代码
,你必须等待
  1. promises
复制代码
数组得到处理。 或者通过
  1. await Promise.all(arrayOfPromises)
复制代码
来完成此操作。
  1. const mapLoop = async _ => {  console.log('Start');  const promises = fruitsToGet.map(async fruit => {    const numFruit = await getNumFruit(fruit);    return numFruit;  });  const numFruits = await Promise.all(promises);  console.log(numFruits);  console.log('End')}
复制代码
运行结果如下:

如果你愿意,可以在
  1. promise
复制代码
中处理返回值,解析后的将是返回的值。
  1. const mapLoop = _ => {  // ...  const promises = fruitsToGet.map(async fruit => {    const numFruit = await getNumFruit(fruit);    return numFruit + 100  })  // ...}“Start”;“[127, 100, 114]”;“End”;
复制代码
[h1]在 filter 循环中使用 await[/h1]当你使用
  1. filter
复制代码
时,希望筛选具有特定结果的数组。假设过滤数量大于20的数组。
如果你正常使用
  1. filter
复制代码
(没有 await),如下:
  1. const filterLoop =  _ => {  console.log('Start')  const moreThan20 =  fruitsToGet.filter(async fruit => {    const numFruit = await fruitBasket[fruit]    return numFruit > 20  })  console.log(moreThan20)   console.log('END')}
复制代码
运行结果
  1. Start["apple"]END
复制代码
  1. filter
复制代码
中的
  1. await
复制代码
不会以相同的方式工作。 事实上,它根本不起作用。
  1. const filterLoop = async _ => {  console.log('Start')  const moreThan20 =  await fruitsToGet.filter(async fruit => {    const numFruit = fruitBasket[fruit]    return numFruit > 20  })  console.log(moreThan20)   console.log('END')}// 打印结果Start["apple", "grape", "pear"]END
复制代码

为什么会发生这种情况?
当在
  1. filter
复制代码
回调中使用
  1. await
复制代码
时,回调总是一个
  1. promise
复制代码
。由于
  1. promise
复制代码
总是真的,数组中的所有项都通过
  1. filter
复制代码
。在
  1. filter
复制代码
使用
  1. await
复制代码
类以下这段代码
  1. const filtered = array.filter(true);
复制代码
  1. filter
复制代码
使用
  1. await
复制代码
正确的三个步骤
  • 使用
    1. map
    复制代码
    返回一个promise 数组
  • 使用
    1. await
    复制代码
    等待处理结果
  • 使用
    1. filter
    复制代码
    对返回的结果进行处理
  1. const filterLoop = async _ => {  console.log('Start');  const promises = await fruitsToGet.map(fruit => getNumFruit(fruit));  const numFruits = await Promise.all(promises);  const moreThan20 = fruitsToGet.filter((fruit, index) => {    const numFruit = numFruits[index];    return numFruit > 20;  })  console.log(moreThan20);  console.log('End')}
复制代码

[h1]在 reduce 循环中使用 await[/h1]如果想要计算
  1. fruitBastet
复制代码
中的水果总数。 通常,你可以使用
  1. reduce
复制代码
循环遍历数组并将数字相加。
  1. const reduceLoop = _ => {  console.log('Start');  const sum = fruitsToGet.reduce((sum, fruit) => {    const numFruit = fruitBasket[fruit];    return sum + numFruit;  }, 0)  console.log(sum)  console.log('End')}
复制代码
运行结果:

当你在
  1. reduce
复制代码
中使用
  1. await
复制代码
时,结果会变得非常混乱。
  1. const reduceLoop = async _ => {  console.log('Start');  const sum = await fruitsToGet.reduce(async (sum, fruit) => {    const numFruit = await fruitBasket[fruit];    return sum + numFruit;  }, 0)  console.log(sum)  console.log('End')}
复制代码
  1. [object Promise]14
复制代码
是什么 鬼??
剖析这一点很有趣。
  • 在第一次遍历中,
    1. sum
    复制代码
    1. 0
    复制代码
    1. numFruit
    复制代码
    1. 27
    复制代码
    (通过
    1. getNumFruit(apple)
    复制代码
    的得到的值),
    1. 0 + 27 = 27
    复制代码

  • 在第二次遍历中,
    1. sum
    复制代码
    是一个
    1. promise
    复制代码
    。 (为什么?因为异步函数总是返回
    1. promises
    复制代码
    !)
    1. numFruit
    复制代码
    1. 0
    复制代码
    .promise 无法正常添加到对象,因此JavaScript将其转换为
    1. [object Promise]
    复制代码
    字符串。
    1. [object Promise] + 0
    复制代码
    1. object Promise] 0
    复制代码

  • 在第三次遍历中,
    1. sum
    复制代码
    也是一个
    1. promise
    复制代码
    1. numFruit
    复制代码
    1. 14
    复制代码
    .
    1. [object Promise] + 14
    复制代码
    1. [object Promise] 14
    复制代码

解开谜团!
这意味着,你可以在
  1. reduce
复制代码
回调中使用
  1. await
复制代码
,但是你必须记住先等待累加器!
  1. const reduceLoop = async _ => {  console.log('Start');  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {    const sum = await promisedSum;    const numFruit = await fruitBasket[fruit];    return sum + numFruit;  }, 0)  console.log(sum)  console.log('End')}
复制代码

但是从上图中看到的那样,
  1. await
复制代码
操作都需要很长时间。 发生这种情况是因为
  1. reduceLoop
复制代码
需要等待每次遍历完成
  1. promisedSum
复制代码

有一种方法可以加速
  1. reduce
复制代码
循环,如果你在等待promisedSum之前先等待
  1. getNumFruits()
复制代码
,那么
  1. reduceLoop
复制代码
只需要一秒钟即可完成:
  1. const reduceLoop = async _ => {  console.log('Start');  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {    const numFruit = await fruitBasket[fruit];    const sum = await promisedSum;    return sum + numFruit;  }, 0)  console.log(sum)  console.log('End')}
复制代码

这是因为
  1. reduce
复制代码
可以在等待循环的下一个迭代之前触发所有三个
  1. getNumFruit
复制代码
promise。然而,这个方法有点令人困惑,因为你必须注意等待的顺序。
在reduce中使用wait最简单(也是最有效)的方法是
  • 使用
    1. map
    复制代码
    返回一个promise 数组
  • 使用
    1. await
    复制代码
    等待处理结果
  • 使用
    1. reduce
    复制代码
    对返回的结果进行处理
  1.     const reduceLoop = async _ => {    console.log('Start');    const promises = fruitsToGet.map(getNumFruit);    const numFruits = await Promise.all(promises);    const sum = numFruits.reduce((sum, fruit) => sum + fruit);    console.log(sum)    console.log('End')    }
复制代码
这个版本易于阅读和理解,需要一秒钟来计算水果总数。

[h1]从上面看出来什么[/h1]
  • 如果你想连续执行
    1. await
    复制代码
    调用,请使用
    1. for
    复制代码
    循环(或任何没有回调的循环)。
  • 永远不要和
    1. forEach
    复制代码
    一起使用
    1. await
    复制代码
    ,而是使用
    1. for
    复制代码
    循环(或任何没有回调的循环)。
  • 不要在
    1. filter
    复制代码
    1. reduce
    复制代码
    中使用
    1. await
    复制代码
    ,如果需要,先用
    1. map
    复制代码
    进一步骤处理,然后在使用
    1. filter
    复制代码
    1. reduce
    复制代码
    进行处理。
文章来自 sf 的小智,有兴趣可以关注他的公众号「大迁世界」 原文链接:https://segmentfault.com/a/1190000019357943
「一个有温度的前端号」

长按识别二维码关注


点赞分享是对作者最大的支持!
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

下载期权论坛手机APP