在上一篇文章【JavaScript | for 語句詳解】提到了在循環(huán)中應(yīng)用 async/await 的例子祭隔。
于是嗅定,順道提一下在 Array.prototype.forEach()
使用 async/await
的問題拿霉。其實(shí)戈次,在 MDN 上就有提醒:
如果使用
promise
或async
函數(shù)作為forEach()
等類似方法的callback
參數(shù)球榆,最好對造成的執(zhí)行順序影響多加考慮厦画,否則容易出現(xiàn)錯誤假哎。
示例:
let sum = 0
const arr = [1, 2, 3]
async function sumFn(a, b) {
return a + b
}
// 為了方便后續(xù)修正改造妓湘,將 forEach 邏輯放到函數(shù) main 中執(zhí)行了玷禽。
function main(array) {
array.forEach(async item => {
sum = await sumFn(sum, item)
})
console.log(sum) // 0, Why?
}
main(arr)
為什么 sum
打印結(jié)果是 0
赫段,而不是預(yù)期的 6
呢?
首先矢赁,我們要理解
async
函數(shù)的語義糯笙,它表示函數(shù)中有異步操作,await
則表示其后面的表達(dá)式需要等待結(jié)果撩银,函數(shù)最終返回一個Promise
對象给涕。
當(dāng)代碼執(zhí)行到 forEach
時:
1. 首先遇到 `sum = await sumFn(sum, item)` 語句(注意,它是從右往左執(zhí)行的)
因此额获,它會執(zhí)行 `sumFn(0, 1)`够庙,那么該函數(shù) `return 1`,
由于 async 函數(shù)始終會返回一個 Promise 對象抄邀,即 `return Promise.resolve(1)`耘眨。
2. 由于 await 的原因,它其實(shí)相當(dāng)于執(zhí)行 `Promise.resolve(3).then()` 方法境肾,
它屬于微任務(wù)剔难,會暫時 Hold 住胆屿,被放入微任務(wù)的隊(duì)列,待本次同步任務(wù)執(zhí)行完之后偶宫,
才會被執(zhí)行非迹,因此并不會立即賦值給 sum(所以 sum 仍為 0)。
3. 那 JS 引擎主線程不會閑著的纯趋,它會繼續(xù)執(zhí)行“同步任務(wù)”彻秆,即下一次循環(huán)。
同理结闸,又將 `return Promise.resolve(2)` 放入微任務(wù)隊(duì)列唇兑。
直到最后一次循環(huán),同樣的的還是 `return Promise.resolve(3)`桦锄。
其實(shí)到這里扎附,forEach 其實(shí)算是執(zhí)行完了。
以上示例结耀,forEach 的真正意義是創(chuàng)建了 3 個微任務(wù)留夜。
4. 由于主線程會一直執(zhí)行同步任務(wù),待同步任務(wù)執(zhí)行完之后图甜,才會執(zhí)行任務(wù)隊(duì)列里面的微任務(wù)碍粥。
待 forEach 循環(huán)結(jié)束之后,自然會執(zhí)行 `console.log(sum)`黑毅,
但注意嚼摩,由于 await 的原因,sum 一直未被重新賦值矿瘦,因此 sum 還是為 0 枕面,
所以控制臺輸出了 0。
5. 等 `console.log(sum)` 執(zhí)行完畢缚去,才開始執(zhí)行隊(duì)列中的微任務(wù)潮秘,
其中 `await Promise.resolve(0)` 的結(jié)果,
相當(dāng)于 `Promise.resolve(0)` 的 then 方法的返回值易结,
所以此前的三個微任務(wù)枕荞,相當(dāng)于:
`sum = 1`
`sum = 2`
`sum = 3`
它們被依次執(zhí)行。
6. 因此 sum 最終的值變成了 3(注意不是 6 哦)搞动。
所以躏精,在 forEach
中使用 async/await
可能沒辦法到達(dá)預(yù)期目的哦。
如何解決以上問題呢滋尉?
我們可以使用 for...of 來替代:
let sum = 0
const arr = [1, 2, 3]
async function sumFn(a, b) {
return a + b
}
// await 要放在 async 函數(shù)中
async function main(array) {
for (let item of array) {
sum = await sumFn(sum, item)
}
console.log(sum) // 6
}
main(arr)
這樣就能輸出預(yù)期結(jié)果 6
了玉控。
那為什么 for...of
就可以呢飞主?因?yàn)樗举|(zhì)上就是一個 while
循環(huán)狮惜。
let sum = 0
const arr = [1, 2, 3]
async function sumFn(a, b) {
return a + b
}
// await 要放在 async 函數(shù)中
async function main(array) {
// for (let item of array) {
// sum = await sumFn(sum, item)
// }
// 相當(dāng)于
const iterator = array[Symbol.iterator]()
let iteratorResult = iterator.next()
while (!iteratorResult.done) {
sum = await sumFn(sum, iteratorResult.value)
iteratorResult = iterator.next()
}
console.log(sum) // 6
}
main(arr)
只要了解了 async/await
和 for...of
的內(nèi)部運(yùn)行機(jī)制高诺,分析起來就不難了。
The end.