一、问题场景
function showData(data) {
// data是个数组 里面的值需要转换后再进行展示
data.forEach(async t =>
{const a = await myformatter(t);
console.log(a)}
);
console.log('全部转换完毕')
}
const myformatter = (t)=> new Promise(reslove=> {
setTimeout(() => reslove(t + 1),100)
})
const data = [0,1,2]
showData(data)
这么写,我们预期的结果是:
1
2
3
全部转换完毕
但是实际结果是:
全部转换完毕
1
2
3
二、原因
forEach的实现大概可以抽象为
Array.prototype.forEach = function (callback) {
for (let index = 0; index < this.length; index++) {
callback(this[index], index, this);
}
};
从上述代码中我们可以发现,forEach 只是简单的执行了下回调函数而已,并不会去处理异步的情况。并且你在 callback 中即使使用 break 也并不能结束遍历。
所以上诉代码的实际执行顺序是:
const data = [0,1,2];
data.forEach(....)
=>
首先会遇到 `const a = await myformatter(t)语句`
由于await语法是从右到左执行
因此它会执行 new Promise(reslove => setTimeout(() => reslove(t+1),100))
同时
由于async函数始终会返回一个promise对象,即'return Promise.reslove(new Promise....)' ? 错了,因为myformatter返回了promise对象,那么async并不会再嵌套一个promise对象,则最终返回 return Promise.reslove(setTimeout(t+1,100))
由于await的原因,它其实相当于执行了Promise.reslove(setTimeout(t+1,100)).then() 方法。这是个微任务,会被hold住并放入事件栈的后面,等待本次同步任务执行完之后,才被执行
而JS引擎主线流程并不会闲置,它会继续执行同步任务,同时继续吧下一个t的return Promise.reslove(setTimeout(t+1,100))放到微服务。直到所有同步任务执行完毕
等待console.log('全部转换完毕')执行完毕后,才开始执行微任务。
相关面试题:
let sum = 0
const arr = [1, 2, 3]
async function sumFn(a, b) {
return a + b
}
function main(array) {
array.forEach(async item => {
sum = await sumFn(sum, item)
console.log(sum);
})
console.log(sum);
}
main(arr)
输出结果
0
1
2
3
首先forEach之后的console.log(sum)被执行,此时forEach纷发的三个微任务仍未执行,则输出0
其次执行微任务
await sumFn(0,1)
await sumFn(0,2)
await sumFn(0,3)
三、解决方法
1、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)
}
console.log(sum) // 6
}
main(arr)
1
3
6
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)
console.log(sum)
iteratorResult = iterator.next()
}
console.log(sum) // 6
}
main(arr)
2、forEach可以用map代替(为什么用map,因为forEach无返回值,map能把await/async生成的promise对象返回到promise.all里),利用Promise.all进行等待。
请注意 map+Promise.all无法解决上面这个sum a+b的问题。因为只有promise.all是同步的,它接受了几个promise微任务,而微任务生成的时候,sum是一直没有被改变的(因为promise是微任务)。
但是map+promise可以解决问题场景里的那个问题
async function showData(data) {
// data是个数组 里面的值需要转换后再进行展示
await Promise.all(data.map(async t =>
{const a = await myformatter(t);
console.log(a)}
))
console.log('全部转换完毕')
}
const myformatter = (t)=> new Promise(reslove=> {
setTimeout(() => reslove(t + 1),100)
})
const data = [0,1,2]
showData(data)
输出:
1
2
3
全部转换完毕
保证了data数据转换完毕后再往下走。
但是有一个缺陷,promise.all并不能保证promise完成顺序,如果要做按序累加的话,就不可以用promise.all了,只能用for of来保证顺序