前言
提一下在 Array.prototype.forEach()
使用 async/await
的问题。其实,在 MDN 上就有提醒:
如果使用
promise
或async
函数作为forEach()
等类似方法的callback
参数,最好对造成的执行顺序影响多加考虑,否则容易出现错误。
正文
示例:
let sum = 0 const arr = [1, 2, 3] async function sumFn(a, b) { return a + b } // 为了方便后续修正改造,将 forEach 逻辑放到函数 main 中执行了。 function main(array) { array.forEach(async item => { sum = await sumFn(sum, item) }) console.log(sum) // 0, Why? } main(arr)
为什么 sum
打印结果是 0
,而不是预期的 6
呢?
首先,我们要理解
async
函数的语义,它表示函数中有异步操作,await
则表示其后面的表达式需要等待结果,函数最终返回一个Promise
对象。
当代码执行到 forEach
时:
1. 首先遇到 `sum = await sumFn(sum, item)` 语句(注意,它是从右往左执行的) 因此,它会执行 `sumFn(0, 1)`,那么该函数 `return 1`, 由于 async 函数始终会返回一个 Promise 对象,即 `return Promise.resolve(1)`。 2. 由于 await 的原因,它其实相当于执行 `Promise.resolve(3).then()` 方法, 它属于微任务,会暂时 Hold 住,被放入微任务的队列,待本次同步任务执行完之后, 才会被执行,因此并不会立即赋值给 sum(所以 sum 仍为 0)。 3. 那 JS 引擎主线程不会闲着的,它会继续执行“同步任务”,即下一次循环。 同理,又将 `return Promise.resolve(2)` 放入微任务队列。 直到最后一次循环,同样的的还是 `return Promise.resolve(3)`。 其实到这里,forEach 其实算是执行完了。 以上示例,forEach 的真正意义是创建了 3 个微任务。 4. 由于主线程会一直执行同步任务,待同步任务执行完之后,才会执行任务队列里面的微任务。 待 forEach 循环结束之后,自然会执行 `console.log(sum)`, 但注意,由于 await 的原因,sum 一直未被重新赋值,因此 sum 还是为 0 , 所以控制台输出了 0。 5. 等 `console.log(sum)` 执行完毕,才开始执行队列中的微任务, 其中 `await Promise.resolve(0)` 的结果, 相当于 `Promise.resolve(0)` 的 then 方法的返回值, 所以此前的三个微任务,相当于: `sum = 1` `sum = 2` `sum = 3` 它们被依次执行。 6. 因此 sum 最终的值变成了 3(注意不是 6 哦)。
所以,在 forEach
中使用 async/await
可能没办法到达预期目的哦。
如何解决以上问题呢?
我们可以使用 for...of 来替代:
let sum = 0 const arr = [1, 2, 3] async function sumFn(a, b) { return a + b } // await 要放在 async 函数中 async function main(array) { for (let item of array) { sum = await sumFn(sum, item) } console.log(sum) // 6 } main(arr)
这样就能输出预期结果 6
了。
那为什么 for...of
就可以呢?因为它本质上就是一个 while
循环。
let sum = 0 const arr = [1, 2, 3] async function sumFn(a, b) { return a + b } // await 要放在 async 函数中 async function main(array) { // for (let item of array) { // sum = await sumFn(sum, item) // } // 相当于 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
的内部运行机制,分析起来就不难了。
The end.