需求
例如现在有一个场景,获取到了一堆id存放在数组里,然后要逐个通过异步请求获取到id对应的数据。
我们可能会为了图方便,使用forEach
对id数组进行遍历,然后将id传入到请求方法中,最后遍历完成后打印一个“打印完毕”。
// 模拟请求方法(模拟网络请求,所以加了1秒钟的定时器)
function delayLog(item) {
return new Promise(resolve => {
setTimeout(() => {
resolve(item)
}, 1000)
})
}
const ids = [1,2,3,4]
// 请求id对应的数据
function processArr(arr) {
arr.forEach(async item => {
console.log(await delayLog(item))
})
console.log("打印完毕")
}
processArr(ids)
但是最后发现,打印完毕直接最先就显示在了控制台,然后一秒钟后结果全部显示,这明显不符合我们的预期。
结果分析
之所以会出现这样的结果,我们可以通过代码分析出在执行processArr函数的时候,console.log是同步代码
,而forEach里边的回调函数是异步代码
。只要了解过浏览器事件循环机制,宏任务和微任务的都知道,同步代码会最先执行,所以这就是造成这一结果的根本原因。
解决办法
我们可以将forEach
改为for of
,在for of
里边调用请求的函数,使用await
接收结果,将整个processArr
函数改为async
函数
function delayLog(item) {
return new Promise(resolve => {
setTimeout(() => {
resolve(item)
}, 1000)
})
}
const ids = [1,2,3,4]
async function processArr(arr) {
for(let item of arr) {
console.log(await delayLog(item))
}
console.log("打印完毕")
}
processArr(ids)
运行结果:
结果分析
如果我们将processArr
函数改为async
函数之后,再在内部的for of
循环中使用await
接收数据,那么此时就必须按照async函数的执行规则,也就是上一轮的await没有执行完成之前都不会执行下边的代码,直至for of
循环中的await全部执行完成,才会执行下边的console.log,
这样的做法就是以同步代码的方式去解决异步代码的问题。 虽然内部还是异步的方式执行的,但是在代码层面和最终执行结果的层面就完成了同步的逻辑顺序。