循环异步代码中不为人知的秘密

115 阅读2分钟

相信很多小伙伴都会遇到一个接口需要循环调用的情况,可是在js中循环异步代码是有个大大的陷阱的你知道吗?

const arr = [1,2,3,4,5]
arr.forEach(async (item) =>{
    await awaitTime(item * 1000)
    console.log(item)
})
console.log('循环完成')

像这段代码,可能就有小伙伴想当然的以为会先打印12345再打印循环完成。其实恰恰相反,为什么呢? 我们把这段代码换个写法想必你就明白了。

const arr = [1,2,3,4,5]
const fun = async (item)=>{
    await awaitTime(item * 1000)
    console.log(item)
}
fun(1)
fun(2)
fun(3)
fun(4)
fun(5)
console.log('循环完成')

forEach中的每个回调函数,在执行的时候,我们都没有添加await 等待,所以他的运行逻辑就和上述代码是一样的。这样循环完成先打印是不是就很清晰了呢。

for循环解决方案

那么问题来了 我就是需要循环中的接口按照顺序上一个结束 ,下一个再执行怎么办呢。这种情况用原生的for即可。

const fun = async ()=>{
    const arr = [1,2,3,4,5]
    for (let i = 0; i < arr.length; i++) {
        await awaitTime(i * 1000)
        console.log(i)
    }
    console.log('循环完成')
}
fun()

这样就是符合我们预期的先打印01234在打印循环完成。像这段代码 他的执行逻辑就是这样的

const fun = async ()=>{
    const arr = [1,2,3,4,5]
    await awaitTime(0 * 1000)
    console.log(0)
    await awaitTime(1 * 1000)
    console.log(1)
    await awaitTime(2 * 1000)
    console.log(2)
    await awaitTime(3 * 1000)
    console.log(3)
    await awaitTime(4 * 1000)
    console.log(4)
    console.log('循环完成')
}
fun()

Promise.all解决方案

其实在大多数情况下我们并不是需要 循环中的接口按照顺序上一个结束 ,下一个再执行。我们需要的是循环中的接口全部执行完毕再执行后续的代码逻辑。这样我们的接口可以同时调用 减少总体的调用时间。不太了解Promise的可以去这里看看。传送门

值得一提的是,Promise.all传入的promise数组中如果有任意一个出现了失败的状态,其他未执行完的promise任务将不再执行。因此如果你的业务如果会出现这种情况的话 可以使用Promise.allSettled这个api。无论失败还是成功都会将结果返回。

const arr = [1,2,3,4,5]
const promiseArr = []
arr.forEach((item) =>{
    promiseArr.push(awaitTime(item * 1000))
    console.log(item)
})
Promise.all(promiseArr).then(data =>{
    console.log('循环完成')
})
//or
Promise.allSettled(promiseArr).then(data =>{
    console.log('循环完成')
})