前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何在 JS 循环中正确使用 async 与 await

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

作者头像
前端小智@大迁世界
发布于 2019-06-15 06:59:30
发布于 2019-06-15 06:59:30
5.3K00
代码可运行
举报
文章被收录于专栏:终身学习者终身学习者
运行总次数:0
代码可运行

准备一个例子

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fruitBasket = {
 apple: 27,
 grape: 0,
 pear: 14
};

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const getNumFruit = fruit => {
  return fruitBasket[fruit];
};

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

在 for 循环中使用 await

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fruitsToGet = [“apple”, “grape”, “pear”];

循环遍历这个数组:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const forLoop = async _ => {
  console.log('Start');
  
  for (let index = 0; index < fruitsToGet.length; index++) {
    // 得到每个水果的数量
  }

  console.log('End')
}

for循环中,过上使用getNumFruit来获取每个水果的数量,并将数量打印到控制台。

由于getNumFruit返回一个promise,我们使用 await 来等待结果的返回并打印它。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

当使用await时,希望JavaScript暂停执行,直到等待 promise 返回处理结果。这意味着for循环中的await 应该按顺序执行。

结果正如你所预料的那样。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
“Start”;
“Apple: 27;
“Grape: 0;
“Pear: 14;
“End”;

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

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

在 forEach 循环中使用 await

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const forEach = _ => {
  console.log('start');

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

  console.log('End')
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const forEachLoop = _ => {
  console.log('Start');

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

  console.log('End')
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
“Start”;27;0;14;
“End”;

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

实际控制台打印如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
‘Start’
‘End’
‘27’
‘0’
‘14

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

在 map 中使用 await

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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”;

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

运行结果如下:

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的数组。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

运行结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Start
["apple"]
END

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

为什么会发生这种情况?

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const filtered = array.filter(true);

filter使用 await 正确的三个步骤

  1. 使用map返回一个promise 数组
  2. 使用 await 等待处理结果
  3. 使用 filter 对返回的结果进行处理
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
} 

在 reduce 循环中使用 await

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

运行结果:

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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')
}

[object Promise]14 是什么 鬼??

剖析这一点很有趣。

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

解开谜团!

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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')
}

这是因为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') }

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

从上面看出来什么

  1. 如果你想连续执行await调用,请使用for循环(或任何没有回调的循环)。
  2. 永远不要和forEach一起使用await,而是使用for循环(或任何没有回调的循环)。
  3. 不要在 filterreduce 中使用 await,如果需要,先用 map 进一步骤处理,然后在使用 filterreduce 进行处理。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
MongoDB数据库基本操作
安装 mongodb MongoDBcompass 配置mongoose npm install mongoose node 连接数据库 const mongoose = require('mongoose');<br/> mongoose.connect('mongodb://localhost/playground', { useNewUrlParser: true })<br/> .then( () => console.log('数据库连接成功'))<br/> .catch( err
用户3461357
2019/08/20
4.6K0
使用Mongoose的populate方法实现多表关联查询
MongoDB在3.2以上的版本有类似于 join 的 $lookup 聚合操作符,其实 Mongoose 有一个更强大的替代方法,叫做populate ( ),它允许你在其他集合中引用文档,实现更简洁优雅的查询操作。
越陌度阡
2020/11/26
3.9K0
使用Mongoose的populate方法实现多表关联查询
MongoDB增删改查操作
创建集合分为两步,-是对对集合设定规则,二是创建集合,创建mongoose.Schema构造函数的实例即可创建集合。
清出于兰
2022/01/05
6.5K0
MongoDB增删改查操作
MongoDB增删改查操作
创建集合分为两步,一是对对集合设定规则,二是创建集合,创建mongoose.Schema构造函数的实例即可创建集合。
Qwe7
2022/05/22
1.6K0
MongoDB增删改查操作
创建集合分为两步,一是对对集合设定规则,二是创建集合,创建mongoose.Schema构造函数的实例即可创建集合。
梨涡浅笑
2020/10/30
20.2K0
MongoDB增删改查操作
如何使用Mongoose创建一个数据处理的模块
Schema 是 Mongoose 中定义文档结构的方式。在model/index.js中定义 Schema
炑焽
2025/01/11
4381
如何使用Mongoose创建一个数据处理的模块
Mongoose模块化实践
Mongoose为操作MongoDB数据库提供了很大的方便,在实际开发过程中,为了保证可扩展与可维护性,通常会将Mongoose进行模块化,下面记录一个模块化的实例,便于在以后的项目中复用。
越陌度阡
2020/11/26
1.1K0
使用 Mongoose 基于 MongoDB 建模并设置关联
首先把 Post 和 Category 找出来,然后把分类字段改掉,最后别忘了保存。
Innei
2021/12/28
1.8K0
前后端演示SHA1,MD5加密登录(带数据库)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/j_bleach/article/details/74131038
j_bleach
2019/07/02
1.3K0
前后端演示SHA1,MD5加密登录(带数据库)
MongDB删除文档和更新文档
4、删除文档和更新文档 // 删除单个 Course.findOneAndDelete({}).then(result => console.log(result)) // 删除多个 User.deleteMany({}).then(result => console.log(result)) // 引入mongoose第三方模块 用来操作数据库 const mongoose = require('mongoose'); // 数据库连接 mongoose.connect('mongodb://loca
Qwe7
2022/05/23
3K0
MongoDB增删改查操作
创建集合分为两步,-是对对集合设定规则,二是创建集合,创建mongoose.Schema构造函数的实例即可创建集合。
星辰_大海
2020/11/04
6.9K0
MongoDB增删改查操作
【架构师(第四十篇)】 服务端开发之连接 Mongodb 数据库
创建一个 imooc_lego_course 数据库,一个 work collection。
一尾流莺
2022/12/10
1.4K0
【架构师(第四十篇)】 服务端开发之连接 Mongodb  数据库
MongoDB x Mongoose: 实现类似 Join 的功能
官方示例 其他 当前的实现全部都是基于 Mongoose 完成的 官方示例 var mongoose = require('mongoose'), Schema = mongoose.Schema var PersonSchema = new Schema({ name: String, age: Number, stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }] }); var StoryS
szhshp
2022/09/21
9760
MongoDB查找文档
3、查找文档 // 根据条件查找文档(条件为空则查找所有文档) Course.find().then(result => console.log(result)) // 返回文档集合 [{ _id: 5c0917ed37ec9b03c07cf95f, name: 'node.js基础', author: 'wuyuxin‘ },{ _id: 5c09dea28acfb814980ff827, name: 'Javascript', author: 'wuyuxin‘ }]
Qwe7
2022/05/23
2.8K0
Mongoose 实现关联查询和踩坑记录
本文源自工作中的一个问题,在使用 Mongoose 做关联查询时发现使用 populate() 方法不能直接关联非 _id 之外的其它字段,在网上搜索时这块的解决方案也并不是很多,在经过一番查阅、测试之后,有两种可行的方案,使用 Mongoose 的 virtual 结合 populate 和 MongoDB 原生提供的 Aggregate 里面的 $lookup 阶段来实现。
五月君
2020/08/20
27K0
Mongoose 实现关联查询和踩坑记录
百度地图用到的geojson类型LineString文档Schema [mongoose]
代码如下: const mongoose = require('mongoose'); mongoose.connect('mongodb://127.0.0.1:81192/lets-go-brandon',{ useNewUrlParser: true, useUnifiedTopology: true }) const db = mongoose.connection; db.on('error',()=>{ console.log('***数据库连接失败***') }
周星星9527
2021/11/03
1.2K0
Nodejs学习笔记(十四)— Mongoose介绍和入门
简介   Mongoose是在node.js异步环境下对mongodb进行便捷操作的对象模型工具   那么要使用它,首先你得装上node.js和mongodb,关于mongodb的安装和操作介绍可以参考:http://www.cnblogs.com/zhongweiv/p/node_mongodb.html   Github地址:https://github.com/Automattic/mongoose   API Docs:http://mongoosejs.com/docs/guide.html   
Porschev
2018/03/28
3K0
Nodejs学习笔记(十四)— Mongoose介绍和入门
初试MongoDB学习之Mongoose的使用
在MongoDB中,多个Document可以组成Collection(以下简称集合),多个集合又可以组成数据库。我们想要操作MongoDB数据,那就得先要具备上面所说的包含数据的“文档”,文档又是什么意思呢,请看如下介绍。
九旬
2020/10/23
6.3K0
初试MongoDB学习之Mongoose的使用
mongoose食用姿势!
Mongoose库简而言之就是对node环境中MongoDB数据库操作的封装,一种对象模型工具,可以将数据库中的数据转换为JavaScript对象供我们使用。
十月梦想
2018/08/29
1.6K0
Nodejs和Mongodb的连接器Mongoose
今天我们将学习Mongoose,什么是Mongoose呢,它于MongoDB又是什么关系呢,它可以用来做什么呢,介绍Mongoose之前,我们先简单了解一下MongoDB。
笔阁
2018/09/04
6.3K0
相关推荐
MongoDB数据库基本操作
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档