需求

例如现在有一个场景,获取到了一堆id存放在数组里,然后要逐个通过异步请求获取到id对应的数据。
我们可能会为了图方便,使用forEach对id数组进行遍历,然后将id传入到请求方法中,最后遍历完成后打印一个“打印完毕”。

  1. // 模拟请求方法(模拟网络请求,所以加了1秒钟的定时器)
  2. function delayLog(item) {
  3. return new Promise(resolve => {
  4. setTimeout(() => {
  5. resolve(item)
  6. }, 1000)
  7. })
  8. }
  9. const ids = [1,2,3,4]
  10. // 请求id对应的数据
  11. function processArr(arr) {
  12. arr.forEach(async item => {
  13. console.log(await delayLog(item))
  14. })
  15. console.log("打印完毕")
  16. }
  17. processArr(ids)

但是最后发现,打印完毕直接最先就显示在了控制台,然后一秒钟后结果全部显示,这明显不符合我们的预期。
image.png

结果分析

之所以会出现这样的结果,我们可以通过代码分析出在执行processArr函数的时候,console.log是同步代码,而forEach里边的回调函数是异步代码。只要了解过浏览器事件循环机制宏任务微任务的都知道,同步代码会最先执行,所以这就是造成这一结果的根本原因。
image.png

解决办法

我们可以将forEach改为for of,在for of里边调用请求的函数,使用await接收结果,将整个processArr函数改为async函数

  1. function delayLog(item) {
  2. return new Promise(resolve => {
  3. setTimeout(() => {
  4. resolve(item)
  5. }, 1000)
  6. })
  7. }
  8. const ids = [1,2,3,4]
  9. async function processArr(arr) {
  10. for(let item of arr) {
  11. console.log(await delayLog(item))
  12. }
  13. console.log("打印完毕")
  14. }
  15. processArr(ids)

运行结果:

image.png

结果分析

如果我们将processArr函数改为async函数之后,再在内部的for of循环中使用await接收数据,那么此时就必须按照async函数的执行规则,也就是上一轮的await没有执行完成之前都不会执行下边的代码,直至for of循环中的await全部执行完成,才会执行下边的console.log,

这样的做法就是以同步代码的方式去解决异步代码的问题。 虽然内部还是异步的方式执行的,但是在代码层面和最终执行结果的层面就完成了同步的逻辑顺序。

image.png