forEach 如果传入异步回调如何保证并行执行?

简介: forEach 本身是同步的,但是如果回调函数是异步的,那么forEach 会立即执行下一个任务,而不会等待回调函数执行完毕,这个时候如何保证异步任务的串行执行呢?

forEach本身是同步的,但是如果回调函数是异步的,那么forEach会立即执行下一个任务,而不会等待回调函数执行完毕,这个时候如何保证异步任务的串行执行呢?

这是我之前写的一篇文章中提到的问题,文章地址:别再恶搞forEach了,它就是单纯的从头遍历到尾,它没有那么多问题

可恶,没有一个人回答我这个问题,所以我自己分享出来好了。

异步

提到这个问题就要先说一下异步,异步是指程序的执行顺序与代码的书写顺序不一致,比如:

console.log(1)
setTimeout(() => {
   
  console.log(2)
}, 0)
console.log(3)

这段代码的执行顺序是132,而不是123,这就是异步。

异步指的不是回调函数,回调函数是一种异步的实现方式,但是并不是所有的异步都是回调函数,比如PromiseGeneratorAsync/Await等都是异步的实现方式,但是并不是回调函数。

串行

串行是指多个任务按照顺序执行,比如:

console.log(1)
console.log(2)
console.log(3)

这段代码的执行顺序是123,这就是串行。

串行执行异步任务

那么如何保证异步任务的串行执行呢?

1. 递归

const arr = [1, 2, 3]
const asyncForEach = (arr, callback) => {
   
  const loop = (index) => {
   
    if (index === arr.length) return
    callback(arr[index], index, arr, () => {
   
      loop(index + 1)
    })
  }
  loop(0)
}

asyncForEach(arr, (item, index, arr, next) => {
   
  setTimeout(() => {
   
    console.log(item)
    next()
  }, parseInt(Math.random() * 1000))
})

这里使用了递归,每次执行完一个任务后,调用next函数,next函数会执行下一个任务。

2. Promise

const arr = [1, 2, 3]
const asyncForEach = (arr, callback) => {
   
  arr.reduce((prev, cur, index) => {
   
    return prev.then(() => {
   
      return callback(cur, index, arr)
    })
  }, Promise.resolve())
}

asyncForEach(arr, (item, index, arr) => {
   
  return new Promise((resolve) => {
   
    setTimeout(() => {
   
      console.log(item)
      resolve()
    }, parseInt(Math.random() * 1000))
  })
})

这里使用了Promise,每次执行完一个任务后,返回一个PromisePromiseresolve函数会执行下一个任务。

3. Async/Await

const arr = [1, 2, 3]
const asyncForEach = async (arr, callback) => {
   
  for (let i = 0; i < arr.length; i++) {
   
    await callback(arr[i], i, arr)
  }
}

asyncForEach(arr, async (item, index, arr) => {
   
  return new Promise((resolve) => {
   
    setTimeout(() => {
   
      console.log(item)
      resolve()
    }, parseInt(Math.random() * 1000))
  })
})

这里使用了Async/Await,每次执行完一个任务后,使用await等待下一个任务执行完毕。

forEach 中的异步如何实现串行?

上面说了那么多示例,那么forEach中的异步如何实现串行呢?

const arr = [1, 2, 3]
arr.forEach(async (item) => {
   
  setTimeout(() => {
   
    console.log(item)
  }, parseInt(Math.random() * 1000))
})

上面的代码中,forEach中的输出是随机的,你并不知道如何保证输出的顺序,那么如何保证输出的顺序呢?

1. 递归

这个时候有人就问了,forEach你怎么递归?来看看forEach递归的实现:

const arr = [1, 2, 3]
const asyncForEach = (arr) => {
   
   arr.forEach(async (item, index, arr) => {
   
      const newArr = arr.splice(index + 1, arr.length);
      setTimeout(() => {
   
         console.log(item)
         asyncForEach(newArr)
      }, parseInt(Math.random() * 1000))
   })
}
asyncForEach(arr)

这里的原理就是使用splice截取数组,这样就会修改原数组,导致forEach的每次只会迭代一次,但是缺点就是index不会发生变化,同时原数组也会发生变化。

后面两个没必要看了,就是凑字数的,和上面描述的差不多,也没用到forEach,障眼法而已。

2. Promise

const arr = [1, 2, 3]
const asyncForEach = (arr) => {
   
  arr.reduce((prev, cur, index) => {
   
    return prev.then(() => {
   
      return new Promise((resolve) => {
   
        setTimeout(() => {
   
          console.log(cur)
          resolve()
        }, parseInt(Math.random() * 1000))
      })
    })
  }, Promise.resolve())
}

asyncForEach(arr)

这里没有使用forEach,而是使用了reduce,这样就可以保证index不会发生变化,同时原数组也不会发生变化。

reduce会收集每次的Promise,最后返回一个Promise,这样就可以保证每次执行完一个任务后,再执行下一个任务。

3. Async/Await

const arr = [1, 2, 3]
const asyncForEach = async (arr) => {
   
  for (let i = 0; i < arr.length; i++) {
   
    await new Promise((resolve) => {
   
      setTimeout(() => {
   
        console.log(arr[i])
        resolve()
      }, parseInt(Math.random() * 1000))
    })
  }
}

asyncForEach(arr)

这里也没使用forEach,而是使用了for循环,这样就可以保证index不会发生变化,同时原数组也不会发生变化。

总结

异步真好玩,玩的好升职加薪,玩不好提桶走人,今天的分享就到了,不会真的有人在forEach中使用异步回调吧???

目录
相关文章
|
Ubuntu 开发工具
Ubuntu更换阿里云软件源
Ubuntu更换阿里云软件源
143194 0
|
前端开发 JavaScript
解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
该文章教授了如何使用Promise和async/await来解决异步编程问题,从而避免回调地狱,使代码更加清晰和易于管理。
解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
|
10月前
|
数据安全/隐私保护
基于电压电流双闭环控制的三相整流器系统simulink建模与仿真
本课题基于电压电流双闭环控制,对三相整流器系统进行Simulink建模与仿真。系统采用MATLAB2022a版本,通过外环电压和内环电流控制,实现直流侧电压和交流侧电流的精确调节,提高动态响应速度和稳态精度。仿真结果无水印,展示了良好的功率因数和谐波性能。核心模型包括PI控制器用于电流调节,确保电流误差为零,同时引入谐波抑制策略以优化系统性能。
|
算法 安全 前端开发
基于postMessage和BroadcastChannel实现浏览器跨Tab窗口通信的方法介绍
基于postMessage和BroadcastChannel实现浏览器跨Tab窗口通信的方法介绍
450 0
|
资源调度 JavaScript
Vue2拖拽插件(vuedraggable)
这篇文章介绍了如何在Vue 3框架中使用`vuedraggable`插件来实现拖拽功能,并提供了插件的安装、配置和事件处理的示例。
1276 1
Vue2拖拽插件(vuedraggable)
|
C++
【PTA】​L1-078 吉老师的回归​(C++)
【PTA】​L1-078 吉老师的回归​(C++)
304 0
【PTA】​L1-078 吉老师的回归​(C++)
|
JavaScript
vue-demi
vue-demi
371 0
|
JSON 数据格式
Echarts饼状图修改图例legend文字颜色和字体大小
Echarts饼状图修改图例legend文字颜色和字体大小
3362 1
|
人工智能 JSON 自然语言处理
解锁数据潜力:信息抽取、数据增强与UIE的完美融合
解锁数据潜力:信息抽取、数据增强与UIE的完美融合
解锁数据潜力:信息抽取、数据增强与UIE的完美融合
Echarts案例:使用象形柱状图(PictorialBar)实现电池效果柱状图
Echarts案例:使用象形柱状图(PictorialBar)实现电池效果柱状图
1761 1
Echarts案例:使用象形柱状图(PictorialBar)实现电池效果柱状图