爲啥 await 不能用在 forEach 中

不知道你是否寫過相似的代碼:

function test() {
     let arr = [3, 2, 1]
     arr.forEach(async item => {
      const res = await fetch(item)
      console.log(res)
     })
     console.log('end')
    }
    
    function fetch(x) {
     return new Promise((resolve, reject) => {
      setTimeout(() => {
       resolve(x)
      }, 500 * x)
     })
    }
    
    test()
複製代碼

我當時指望的打印順序是數組

3
2
1
end
複製代碼

結果現實與我開了個玩笑,打印順序竟然是bash

end
1
2
3
複製代碼

爲何?異步

其實緣由很簡單,那就是 forEach 只支持同步代碼async

咱們能夠參考下 Polyfill 版本的 forEach,簡化之後相似就是這樣的僞代碼函數

while (index < arr.length) {
      callback(item, index)   //也就是咱們傳入的回調函數
    }
複製代碼

從上述代碼中咱們能夠發現,forEach 只是簡單的執行了下回調函數而已,並不會去處理異步的狀況。 而且你在 callback 中即便使用 break 也並不能結束遍歷。post

怎麼解決?fetch

通常來講解決的辦法有2種,for...of和for循環。ui

使用 Promise.all 的方式行不行,答案是: 不行 spa

async function test() {
    let arr = [3, 2, 1]
    await Promise.all(
     arr.map(async item => {
      const res = await fetch(item)
      console.log(res)
     })
    )
    console.log('end')
   }
複製代碼

能夠看到並無按照咱們指望的輸出。3d

這樣能夠生效的緣由是 async 函數確定會返回一個 Promise 對象,調用 map 之後返回值就是一個存放了 Promise 的數組了,這樣咱們把數組傳入 Promise.all 中就能夠解決問題了。可是這種方式其實並不能達成咱們要的效果,若是你但願內部的 fetch 是順序完成的,能夠選擇第二種方式。

第1種方法是使用 for...of

async function test() {
     let arr = [3, 2, 1]
     for (const item of arr) {
      const res = await fetch(item)
      console.log(res)
     }
     console.log('end')
    }
複製代碼

這種方式相比 Promise.all 要簡潔的多,而且也能夠實現開頭我想要的輸出順序。

可是這時候你是否又多了一個疑問?爲啥 for...of 內部就能讓 await 生效呢。

由於 for...of 內部處理的機制和 forEach 不一樣,forEach 是直接調用回調函數,for...of 是經過迭代器的方式去遍歷。

async function test() {
     let arr = [3, 2, 1]
     const iterator = arr[Symbol.iterator]()
     let res = iterator.next()
     while (!res.done) {
      const value = res.value
      const res1 = await fetch(value)
      console.log(res1)
      res = iterator.next()
     }
     console.log('end')
    }
複製代碼

第2種方法是使用 for循環

async function test() {
  let arr = [3, 2, 1]
  for (var i=0;i<arr.length;i++) {
    const res = await fetch(arr[i])
    console.log(res)
  }
  console.log('end')
}
 
function fetch(x) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   resolve(x)
  }, 500 * x)
 })
}

test()
複製代碼

要想在循環中使用async await,請使用for...of 或者 for 循環

參考連接:

async,await與forEach引起的血案

【JS基礎】從JavaScript中的for...of提及(下) - async和await

相關文章
相關標籤/搜索