1. 程式人生 > 實用技巧 >forEach與async/await使用踩坑

forEach與async/await使用踩坑

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

思考如上程式碼、我們預期結果是先輸出1,2,3,然後因為等待非同步結果最後輸出end

但是實際上輸出卻是end先輸出,才到1,2,3。

原因如下:

1、首先這是因為foreach是沒有return返回值的(可以自己去跟下原始碼,foreach內部實現只是簡單的回撥)

2、而foreach裡面的回撥函式因為加了async的原因,所以預設會返回一個promise,但是因為foreach的實現並沒有返回值,所以導致返回的這個promise物件沒人去管了

首先為了保證end最後輸出,我們肯定要先等待迴圈的返回結果因此改成如下程式碼

async function test() {
 let arr = [1, 2, 3]
 await arr.forEach(async item => {
  const res = await fetch(item)
  console.log(res)
 })
 console.log(
'end') }

但是這樣改之後依然行不通,原因是foreach沒有返回值,所以我們必須保證迴圈能夠有返回值,所以要將foreach改成map

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

結果依然不行,然後我們會發現其實map返回的並不是一個promise物件,而是一個包含promise物件的陣列[promise, promise, promise],其中每個promise物件都是迴圈迭代產生的結果。而await是處理不了陣列的,它只能處理promise物件。考慮到這一點我們基本上就差不多知道如何改正了、有兩種方法。

第一是將迴圈改成常規的遍歷方式

async function test() {
 let arr = [1, 2, 3]
 for(let i in arr){
   const res = await fetch(arr[i])
   console.log(res)              
 }
 console.log('end')
}

第二種就比較高端了,使用Promise.all(),這是一個專門處理promise陣列的方法,當async標記的箭頭函式返回一個promise物件時,map方法得到的就是一個promise物件陣列,然後我們將這個陣列丟給Promise.all()去依次執行,然後只需要使用await去等待執行結果,就能保證後面的end在得到結果後才會被輸出,得到最終輸出結果1,2,3,end

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