需求
例如现在有一个场景,获取到了一堆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,
这样的做法就是以同步代码的方式去解决异步代码的问题。 虽然内部还是异步的方式执行的,但是在代码层面和最终执行结果的层面就完成了同步的逻辑顺序。

