JavaScript循环中如何使用 async/await?需要注意些什么?

风强大大_3883

风强大大_3883

2020-11-30

5201人浏览

转载

async 与 await 的使用方式相对简单。 但当你尝试在循环中使用await时,事情就会变得复杂一些。

在本文中,分享一些在如果循环中使用await值得注意的问题。

准备一个例子

对于这篇文章,假设你想从水果篮中获取水果的数量。

const fruitBasket = {
 apple: 27,
 grape: 0,
 pear: 14
};

你想从fruitBasket获得每个水果的数量。 要获取水果的数量,可以使用getNumFruit函数。

const getNumFruit = fruit => {
  return fruitBasket[fruit];
};

const numApples = getNumFruit('apple');
console.log(numApples); //27

现在,假设fruitBasket是从服务器上获取,这里我们使用 setTimeout 来模拟。

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

最后,假设你想使用await和getNumFruit来获取异步函数中每个水果的数量。

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')
}

1.gif

在 for 循环中使用 await

首先定义一个存放水果的数组:

const fruitsToGet = [“apple”, “grape”, “pear”];

循环遍历这个数组:

const forLoop = async _ => {
  console.log('Start');
  
  for (let index = 0; index <p>在<code>for</code>循环中,过上使用<code>getNumFruit</code>来获取每个水果的数量,并将数量打印到控制台。</p><p>由于<code>getNumFruit</code>返回一个<code>promise</code>,我们使用 <code>await</code> 来等待结果的返回并打印它。</p><pre class="brush:php;toolbar:false;">const forLoop = async _ => {
  console.log('start');

  for (let index = 0; index <p>当使用<code>await</code>时,希望JavaScript暂停执行,直到等待 promise 返回处理结果。这意味着<code>for</code>循环中的<code>await</code> 应该按顺序执行。</p><p>结果正如你所预料的那样。</p><pre class="brush:php;toolbar:false;">“Start”;
“Apple: 27”;
“Grape: 0”;
“Pear: 14”;
“End”;

2.gif

这种行为适用于大多数循环(比如while和for-of循环)…

但是它不能处理需要回调的循环,如forEach、map、filter和reduce。在接下来的几节中,我们将研究await 如何影响forEach、map和filter。

在 forEach 循环中使用 await

首先,使用 forEach 对数组进行遍历。

const forEach = _ => {
  console.log('start');

  fruitsToGet.forEach(fruit => {
    //...
  })

  console.log('End')
}

接下来,我们将尝试使用getNumFruit获取水果数量。 (注意回调函数中的async关键字。我们需要这个async关键字,因为await在回调函数中)。

const forEachLoop = _ => {
  console.log('Start');

  fruitsToGet.forEach(async fruit => {
    const numFruit = await getNumFruit(fruit);
    console.log(numFruit)
  });

  console.log('End')
}

我期望控制台打印以下内容:

“Start”;
“27”;
“0”;
“14”;
“End”;

但实际结果是不同的。在forEach循环中等待返回结果之前,JavaScrip先执行了 console.log('End')。

实际控制台打印如下:

‘Start’
‘End’
‘27’
‘0’
‘14’

3.gif

JavaScript 中的 forEach不支持 promise 感知,也支持 async 和await,所以不能在 forEach 使用 await 。

在 map 中使用 await

如果在map中使用await, map 始终返回promise数组,这是因为异步函数总是返回promise。

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”;

4.png

如果你在 map 中使用 await,map 总是返回promises,你必须等待promises 数组得到处理。 或者通过await Promise.all(arrayOfPromises)来完成此操作。

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')
}

运行结果如下:

5.gif

如果你愿意,可以在promise 中处理返回值,解析后的将是返回的值。

const mapLoop = _ => {
  // ...
  const promises = fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit + 100
  })
  // ...
}
 
“Start”;
“[127, 100, 114]”;
“End”;

在 filter 循环中使用 await

当你使用filter时,希望筛选具有特定结果的数组。假设过滤数量大于20的数组。

javascript-pro
javascript-pro

专注现代 ECMAScript、异步编程、性能优化和全栈的 JavaScript 专家,适用于现代开发

下载

如果你正常使用filter (没有 await),如下:

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')
}

运行结果

Start
["apple"]
END

filter 中的await不会以相同的方式工作。 事实上,它根本不起作用。

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

6.png

为什么会发生这种情况?

当在filter 回调中使用await时,回调总是一个promise。由于promise 总是真的,数组中的所有项都通过filter 。在filter 使用 await类以下这段代码

const filtered = array.filter(true);

在filter使用 await 正确的三个步骤

  1. 使用map返回一个promise 数组
  2. 使用 await 等待处理结果
  3. 使用 filter 对返回的结果进行处理
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')
}

7.gif

在 reduce 循环中使用 await

如果想要计算 fruitBastet中的水果总数。 通常,你可以使用reduce循环遍历数组并将数字相加。

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')
}

运行结果:

8.png

当你在 reduce 中使用await时,结果会变得非常混乱。

 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')
}

9.gif

[object Promise]14 是什么 鬼??

剖析这一点很有趣。

  1. 在第一次遍历中,sum为0。numFruit是27(通过getNumFruit(apple)的得到的值),0 + 27 = 27。
  2. 在第二次遍历中,sum是一个promise。 (为什么?因为异步函数总是返回promises!)numFruit是0.promise 无法正常添加到对象,因此JavaScript将其转换为[object Promise]字符串。 [object Promise] + 0 是object Promise] 0。
  3. 在第三次遍历中,sum 也是一个promise。 numFruit是14. [object Promise] + 14是[object Promise] 14。

解开谜团!

这意味着,你可以在reduce回调中使用await,但是你必须记住先等待累加器!

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')
}

10.gif

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

有一种方法可以加速reduce循环,如果你在等待promisedSum之前先等待getNumFruits(),那么reduceLoop只需要一秒钟即可完成:

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')
}

11.gif

这是因为reduce可以在等待循环的下一个迭代之前触发所有三个getNumFruit promise。然而,这个方法有点令人困惑,因为你必须注意等待的顺序。

在reduce中使用wait最简单(也是最有效)的方法是

  1. 使用map返回一个promise 数组
  2. 使用 await 等待处理结果
  3. 使用 reduce 对返回的结果进行处理

    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')
    }

这个版本易于阅读和理解,需要一秒钟来计算水果总数。

12.gif

从上面看出来什么

  1. 如果你想连续执行await调用,请使用for循环(或任何没有回调的循环)。
  2. 永远不要和forEach一起使用await,而是使用for循环(或任何没有回调的循环)。
  3. 不要在 filter 和 reduce 中使用 await,如果需要,先用 map 进一步骤处理,然后在使用 filter 和 reduce 进行处理。
原文地址:https://medium.com/free-code-camp/javascript-async-and-await-in-loops-30ecc5fb3939 

更多编程相关知识,请访问:编程学习网站!!

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

javascript

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Tailwind CSS 响应式布局怎么写
Tailwind CSS 响应式布局怎么写

Tailwind CSS 响应式布局通常从移动端样式写起,再用 sm、md、lg 等前缀调整不同屏幕宽度下的网格、间距和导航。本专题整理断点配置、Grid 与 Flex 自适应布局及常见溢出问题,帮助你逐步完成多设备适配。

2026.11.03

0

19

Tailwind CSS 常用类名怎么记
Tailwind CSS 常用类名怎么记

Tailwind CSS 类名很多,先按布局、间距、文字、颜色和状态分组记忆,再通过官方文档检索具体写法。本专题结合类名排序、动态拼接失效、自定义实用类等问题,帮助你建立常用类名的速查方法。

2026.11.03

0

12

码上飞入口地址合集
码上飞入口地址合集

本专题汇总了码上飞(CodeFlying)的官方入口地址合集,涵盖官网主站及网页版在线使用入口,支持注册登录后直接在线体验零代码AI应用生成。同时整理了APP端下载渠道与小程序入口,助你快速访问平台,通过自然语言描述需求即可生成应用、小程序或网站。

2026.11.03

0

10

Uvicorn 报错与问题解决教程
Uvicorn 报错与问题解决教程

本专题汇总了Uvicorn服务器常见问题与报错解决的教程合集,涵盖启动失败、端口占用、模块导入错误、事件循环冲突及热重载失效等典型故障的排查思路。同时整理了多进程与Worker配置、SSL/HTTPS证书报错、跨域与代理配置等实战解决方案,助你快速定位并解决Uvicorn运行中的各类问题。

2026.11.02

20

14

Uvicorn使用教程
Uvicorn使用教程

本专题汇总了Uvicorn使用教程与启动运行指南,涵盖其作为Python ASGI服务器的定位与特点,以及命令行启动(uvicorn main:app --reload)、参数配置(host/port/workers)、程序内嵌运行与Gunicorn结合部署等核心用法。同时整理了热重载调试、日志配置与生产环境部署优化等技巧,助你快速掌握Uvicorn的运行与配置。

2026.11.02

20

17

Uvicorn安装配置教程
Uvicorn安装配置教程

本专题汇总了Uvicorn的安装配置教程与部署指南,涵盖通过pip安装Uvicorn、启动FastAPI/Starlette应用、开发模式热重载(--reload)及多进程工作模式配置等核心操作。同时整理了结合Gunicorn的生产部署方案、HTTPS与反向代理配置及性能调优技巧,助你快速完成Uvicorn服务的安装与上线部署。

2026.11.02

20

17

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

120

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

100

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

300

10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

React 教程
React 教程

共58课时 | 12.4万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.8万人学习