[译]async-await 数组循环的几个坑

夏侯杰
• 阅读 7722

[译]async-await 数组循环的几个坑

在 Javascript 循环中使用 async/ await 循环遍历数组似乎很简单,但是在将两者结合使用时需要注意一些非直观的行为。让我们看看三个不同的例子,看看你应该注意什么,以及哪个循环最适合特定用例。

forEach 循环的情况

const urls = [
  'https://jsonplaceholder.typicode.com/todos/1',
  'https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'
];

async function getTodos() {
  await urls.forEach(async (url, idx) => { 
    const todo = await fetch(url);
    console.log(`Received Todo ${idx+1}:`, todo);
  });
  
  console.log('Finished!');
}

getTodos();

[译]async-await 数组循环的几个坑

Finished!
Received Todo 2, Response: { ··· }
Received Todo 1, Response: { ··· }
Received Todo 3, Response: { ··· }

⚠️问题 1:

如上述代码能够正常执行。但是注意 Finished! 消息,被率先打印了。尽管我们使用了 await 但他仍然不会等待所有 await 执行完毕

⚠️ 问题 2:

然而,尽管 await 在循环中使用,但它并没有等待每个请求在执行下一个请求之前完成。请求不会按照顺序一步一步被发送出去。如果第一个请求的时间比以下请求的时间长,它仍然可以在最后完成。

因此,根据上述原因,forEach 在和 async/await 搭配使用的时候并不是一个靠得住的东西

Promise.all 方法

我们首先需要解决的就是等待所有循环执行完毕。await 操作符返回一个 promise,我们可以使用 Promise.all 方法去并行执行所有的请求。最后去 await 所有 promise 返回的结果

const urls = [
  'https://jsonplaceholder.typicode.com/todos/1',
  'https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'
];

async function getTodos() {
  const promises = urls.map(async (url, idx) => 
    console.log(`Received Todo ${idx+1}:`, await fetch(url))
  );

  await Promise.all(promises);

  console.log('Finished!');
}

getTodos();

[译]async-await 数组循环的几个坑

Received Todo 1, Response: { ··· }
Received Todo 2, Response: { ··· }
Received Todo 3, Response: { ··· }
Finished!

我们解决了不等待所有请求执行完毕后打印 Finished!,看起来我们似乎也解决了请求顺序的问题。

实际上,上文中已经提到过,Promise.all 方法会按照并行的模式,将所有请求一次性全部发送出去,然后等待接收到全部结果后,按照顺序打印出来而已。它并不会按照顺序发送一个请求,收到结果后再发送下一个请求。

这非常适合不需要按照顺序发送的情况,但如果你想要的是串行发送请求那么 Promise.all 并不适合

for-of 循环

以上的两种方法并不能完美解决那两个问题。

for-of 循环则能够按照预期顺序执行——等待上一个 await 执行完毕后,再接着下一个。


const urls = [
  'https://jsonplaceholder.typicode.com/todos/1',
  'https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'
];

async function getTodos() {
  for (const [idx, url] of urls.entries()) {
    const todo = await fetch(url);
    console.log(`Received Todo ${idx+1}:`, todo);
  }

  console.log('Finished!');
}

getTodos();

[译]async-await 数组循环的几个坑

Received Todo 1, Response: { ··· }
Received Todo 2, Response: { ··· }
Received Todo 3, Response: { ··· }
Finished!

我特别喜欢这种使代码保持线性的方法,这是使用 async/await 的关键优势之一。我觉得它比其他选择更容易阅读。

如果您不需要访问索引,则代码变得更加简洁:

for(ur url of urls){···}

使用for...of循环的一个主要缺点是它与Javascript中的其他循环选项相比性能不够好。但是,将性能参数用于await异步调用时,性能参数可以忽略不计,因为目的是在每个调用解析之前保持循环。我通常只使用for...of进行异步。

当然你也可以使用 for 循环得到 for-of 循环所有好处。但我还是喜欢 for-of 循环带来的简洁和高可读性。

[译]async-await 数组循环的几个坑

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
JS中arr.forEach()如何跳出循环
示例我们都知道for循环里要跳出整个循环是使用break,但在数组中用forEach循环如要退出整个循环呢?使用break会报错,使用return也不能跳出循环。使用break将会报错:vararr使用return也不能跳出整个循环:vararr那么在用forEach()遍历数组时要如何才能跳出循环呢?经过查找资料后,我找到了两种方法可以实现跳出
皮卡皮卡皮 皮卡皮卡皮
4年前
javaScript 数组常用的几个方法
数组的方法总结1.数组转换字符串1.toString()2.toLocaleString()3.join()jsconstarr1,5,6,9,3,2,4,6console.log('toString'arr.toString())//toString1,5,6,9,3,2,4,6console.log('toLocaleSt
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java开发者容易犯的十个错误
!(https://oscimg.oschina.net/oscnet/c9f00cc918684fbe8a865119d104090b.gif)Top1.数组转换为数组列表将数组转换为数组列表,开发者经常会这样做:\java\List<StringlistArrays.asList(arr);Arr
Wesley13 Wesley13
3年前
Java如何遍历二维数据
/\需求:二维数组遍历外循环控制的是二维数组的长度,其实就是一维数组的个数。内循环控制的是一维数组的长度。\/classArray2Test{publicstaticvoidmain(String\\args){//定义一个二维数组int\\\\arr{{1,2,3}
Stella981 Stella981
3年前
Pinterest:Android系统上的视频管理
Pinterest通过在Android应用中添加适当的视频管理,在为用户提供更加流畅的视频体验的同时,尽可能的为开发人员提供易于使用的视频组件,简化其工作流程。本文来自Pinterest工程博客。文/ GreySkold译/屈健宁原文https://medium.com/pinterestengineering/m
Wesley13 Wesley13
3年前
Java 中 10 大坑爹功能!
今天我们就来聊一下Java中的10大坑爹功能,它们分别是:1.switch必须加上break才结束2.逻辑运算符的“短路”现象3.数组下标从零开始4.ArrayList遍历删除时报错5.字符转成数字的坑6.while循环体的“障眼法”7.Integer类有缓存8.空方法体导致死循环9.神奇的10.Java注
Stella981 Stella981
3年前
JS数组追加数组采用push.apply的坑
JS数组追加数组没有现成的函数,这么多年我已经习惯了a.push.apply(a,b);这种自以为很酷的,不需要写for循环的写法,一直也没遇到什么问题,直到今天我要append的b是个很大的数组时才遇到了坑。a  new Array();     b  new Array(125624);                    
Wesley13 Wesley13
3年前
ES6 展开操作符的几个妙用,老板都说好!
!(https://oscimg.oschina.net/oscnet/2728bcca60c0b9760370ba1a623ea3b5b7d.gif)  持续进步的同学都关注了“1024译站”这是1024译站的第50 篇文章ES6新增了...操作符,通常用于在函数中提取剩余参数和展开数组。但其实它的用途不止于此,本文就介绍几
小万哥 小万哥
1年前
NumPy 数组迭代与合并详解
NumPy数组迭代NumPy数组迭代是访问和处理数组元素的重要方法。它允许您逐个或成组地遍历数组元素。基本迭代我们可以使用Python的基本for循环来迭代NumPy数组。一维数组迭代:pythonimportnumpyasnparrnp.array(1