入夏的杭州, 出门热浪扑面而来,敲代码困意十足。马路上一条条大白腿,琳琅满目,参差不齐。没错,又到了减肥的好时候。(吹牛逼不能超过两句)

进入本文的正题

ES7 引入了asyncawait 方法,来处理异步操作。关于这俩方法的使用,不再赘述,网上的文章多如牛毛,又写一篇入门,未免废话连篇。今天要分享的内容是在 js 的一些循环方法中,async await 的表现是如何的。

for 循环

**
在 for 循环中,async await 表现的还是令人满意。现在假设一个场景,设置一个变量,爸妈我三个人的体重,求和。

  1. const family = {
  2. "dad": 150,
  3. "mom": 100,
  4. "son": 200
  5. }
  6. const familyToGet = ["dad", "mom", "son"]
  7. // 写一个sleep方法
  8. const sleep = ms => {
  9. return new Promise(resolve => setTimeout(resolve, ms))
  10. }
  11. // 假设获取体重数据的过程是一个请求接口的异步过程
  12. const getFamilyWeight = person => {
  13. return sleep(2000).then(() => family[person])
  14. }
  15. const loop_for = async () => {
  16. console.log('start')
  17. let result = 0
  18. for(let i = 0; i < familyToGet.length; i++) {
  19. const person = familyToGet[i]
  20. const weight = await getFamilyWeight(person)
  21. result += weight
  22. }
  23. console.log('result:', result)
  24. console.log('end')
  25. }
  26. loop_for()
  27. // 运行结果
  28. // start
  29. // 150
  30. // 100
  31. // 200
  32. // result: 450
  33. // end

按照上述代码的打印结果分析,for循环内的代码,每一次都会等当前的 getFamilyWeight 方法返回了内容之后,才会继续下一个循环,循环全部结束的时候,才会跑for循环下面的代码,这是我们想要的结果,nice。


forEach 循环

**
forEach 中,async await 表现的差强人意,具体原因,还应归结于 forEach 中的回调函数,具体看看代码的表现。

  1. const family = {
  2. "dad": 150,
  3. "mom": 100,
  4. "son": 200
  5. }
  6. const familyToGet = ["dad", "mom", "son"]
  7. // 写一个sleep方法
  8. const sleep = ms => {
  9. return new Promise(resolve => setTimeout(resolve, ms))
  10. }
  11. // 假设获取体重数据的过程是一个请求接口的异步过程
  12. const getFamilyWeight = person => {
  13. return sleep(2000).then(() => family[person])
  14. }
  15. const loop_forEach = () => {
  16. console.log('start')
  17. let result = 0
  18. // 在回调函数中,异步是不好控制的
  19. familyToGet.forEach(async person => {
  20. const num = await getFamilyWeight(person)
  21. console.log(num)
  22. result += num
  23. })
  24. console.log('result', result)
  25. console.log('end')
  26. }
  27. loop_forEach()
  28. // 运行结果
  29. // start
  30. // result 0
  31. // end
  32. // 150
  33. // 100
  34. // 200

从运行结果中,可以看出,代码没有等待forEach方法执行结束,而是直接继续往后执行了。回调函数传进去之后,2秒钟之后才会返回数据,这不是我们想要的结果,我现在是希望它能顺序的执行。

那么我们分析一下 forEach 内部大致的实现逻辑。

  1. Array.prototype.forEach = function(cb) {
  2. // this 为当前调用该函数的变量
  3. for(let i=0; i < this.length; i++) {
  4. cb(this[i], i);
  5. }
  6. }

且看上面的简易 forEach 重写,其实内部也是一个 for 循环,那么为什么不能表现的和 for 循环一样的运行结果呢?

我们的回调函数进入 forEach 之后,又被单独的以 cb() 这种形式执行了一次,相当于在内部又创建了一个 async await 形式的方法,当f orEach 函数执行完的时候,相当于创建了 3 个方法,这 3 个方法需要等待 2 秒钟,数据才会返回,而 forEach 外面的代码是不会等这 3 个函数返回内容再执行,而是一意孤行的管自己走了,所以才会造成这样的局面。返回问题的根本,我现在的需求是想要代码能顺序执行,且最后能算出一家人体重之和,下面我们来修改一下代码。

  1. const family = {
  2. "dad": 150,
  3. "mom": 100,
  4. "son": 200
  5. }
  6. const familyToGet = ["dad", "mom", "son"]
  7. // 写一个sleep方法
  8. const sleep = ms => {
  9. return new Promise(resolve => setTimeout(resolve, ms))
  10. }
  11. // 假设获取体重数据的过程是一个请求接口的异步过程
  12. const getFamilyWeight = person => {
  13. return sleep(2000).then(() => family[person])
  14. }
  15. const loop_forEach = async () => {
  16. console.log('start')
  17. let promise = []
  18. // 在回调函数中,异步是不好控制的
  19. familyToGet.forEach(person => {
  20. const num = getFamilyWeight(person)
  21. promise.push(num)
  22. })
  23. const result = await Promise.all(promise)
  24. console.log('result', result)
  25. const weight = result.reduce((sum, personWeight) => sum + personWeight)
  26. console.log('weight', weight)
  27. console.log('end')
  28. }
  29. loop_forEach()
  30. // 运行结果
  31. // start
  32. // result [150, 100, 200]
  33. // weight 450
  34. // end

f orEach 的回调函数内,直接把 getFamilyWeight 方法返回的 promise 对象 push 到 promise 这个数组变量内,通过 Promise.all 来做一层等待的异步处理。

map 遍历

**
map 中,直接返回一个 promise 数组。

  1. const family = {
  2. "dad": 150,
  3. "mom": 100,
  4. "son": 200
  5. }
  6. const familyToGet = ["dad", "mom", "son"]
  7. // 写一个sleep方法
  8. const sleep = ms => {
  9. return new Promise(resolve => setTimeout(resolve, ms))
  10. }
  11. // 假设获取体重数据的过程是一个请求接口的异步过程
  12. const getFamilyWeight = person => {
  13. return sleep(2000).then(() => family[person])
  14. }
  15. const loop_map = async () => {
  16. console.log('start')
  17. // map中返回的是一个promise数组,需要通过Promise.all处理
  18. const promise = familyToGet.map(async person => {
  19. const num = await getFamilyWeight(person)
  20. return num;
  21. })
  22. console.log('promise', promise)
  23. // 等待promise里的内容全部返回,才会继续往下执行
  24. const result = await Promise.all(promise)
  25. console.log('result', result)
  26. const weight = result.reduce((sum, personWeight) => sum + personWeight)
  27. console.log('weight', weight)
  28. console.log('end')
  29. }
  30. loop_map()
  31. // 运行结果
  32. // start
  33. // promise [Promise, Promise, Promise]
  34. // result [150, 100, 200]
  35. // weight 450
  36. // end

大家应该能感觉到其实这个和上面改版后的 forEach 差不多,没错,正如你所料,只不过 map 方法能返回新的数组罢了,forEach 则不能返回数组。


在这里把 map 的简单实现写一下,方便理解。

  1. Array.prototype.map = function(cb) {
  2. // this 为当前调用该函数的变量
  3. var o = [];
  4. for(let i=0; i < this.length; i++) {
  5. var temp = cb(this[i], i);
  6. o.push(temp);
  7. }
  8. return o;
  9. }

如上述代码,在外面通过 o 这个变量收集 cb() 回调函数的返回值,再到外面统一处理。真是妙啊,大家可以细细品味一番,能吾出很多新的对象。

总结

1、for循环内使用async和await还是可以的,稳如老狗
2、不要在forEach方法内使用async、await,尽量避免这种写法,坑啊。。。