[JS] Promise 异步的并行与串行调用

[JS] Promise 异步的并行与串行调用

·

2 min read

一直以来,我都是使用promise对接口调用进行处理,随着使用的场景逐渐复杂,发现自己对promise的使用的理解并不到位,所以整理一篇笔记来做个归纳,加深自己的理解

一、promise 的异步执行

首先讲一下promise的异步执行机制,想必大家都听说过:异步任务会放到任务队列中,等待同步任务执行完后再执行;并且你可以使用async/await来阻塞promise异步代码,那么我们来看下面的一段代码

const promise = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}

const func1 = async () => {
    await promise()
    console.log(1)
}
func1()

const func2 = async () => {
    await promise()
    console.log(2)
}
func2()

这段代码的 1 和 2 会同时输出,再来看下一段代码

const promise = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}

const func1 = async () => {
    await promise()
    console.log(1)
    await promise()
    console.log(2)
}
func1()

此时,2 会比 1 晚一秒才输出,所以可以发现async/await仅在它们各自的作用域下会有阻塞的现象

其实async/await就是then的语法糖,我们可以将上面的两段代码翻译一下:

// 第一段
promise().then(() => console.log(1))
promise().then(() => console.log(2))

// 第二段
promise().then(() => {
    console.log(1)
    promise().then(() => {
        console.log(2)
    })
})

这样其实就更加清晰了,第二段代码实际上就是一个promise的嵌套,而第一段是并行的

二、promise 在循环中的串行执行

只有对promise理解足够清晰,才能在promise循环调用中正确判断是否会阻塞代码执行

先来看看下面这段代码,在这段代码中,1、2、3、4、5 会同时输出

const promise = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}

const arr = [1, 2, 3, 4, 5]
let res = Promise.resolve()
arr.forEach(async (i) => {
    res = await promise()
    console.log(i)
})

这段代码其实就是上面第一点的第一段代码,forEach只会同步地执行它的回调,回调里面的异步内容的作用域也仅限于这个回调函数,await并不能阻塞等待这个函数执行完才执行下一个,而是被直接丢到任务队列

然后我们再来看一段代码,在这段代码中,每个数字的输出都会间隔 1 秒

const promise = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}

const arr = [1, 2, 3, 4, 5]
let res = Promise.resolve()
arr.forEach((i) => {
    res = res.then(async () => {
           console.log(i)
           await promise()
    })
})

仔细想想,这一段其实就是在链式地调用then,所以起到了阻塞的作用:

res = promise()
    .then(() => { console.log(1); return promise() })
    .then(() => { console.log(2); return promise() })
    .then(() => { console.log(3); return promise() })
    .then(() => { console.log(4); return promise() })
    .then(() => { console.log(5); return promise() })

所以,总结一下,promise的串行调用实际上的实现就是then的链式调用

此外,还有一种利用reduce的巧妙写法,这种其实和上面第一种写法是等价的:

arr.reduce((pre, item) => (
    // 所有的逻辑都写在这个 then 里面
    pre.then(async () => {
        await promise()
        console.log(item)
    })
), Promise.resolve())

那么,在循环中使用await来就无法实现实现串行执行吗,其实可以用while循环来做:

const func1 = async () => {
    let i = 0
    while(i < arr.length) {
        await promise()
        console.log(arr[i])
        i++
    }
    console.log("while循环结束")
}
func1()

可以发现每隔一秒才会有输出,并且最后一句结束输出是在while循环完了之后才执行的

三、promise 在循环中的并行执行

第二点中的第一个例子就是常见的做法,在每个函数内使用await,阻塞该函数内部的代码,而不会阻塞下一个循环的函数的代码

const promise = () => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}

const arr = [1, 2, 3, 4, 5]
let res = Promise.resolve()
arr.forEach(async (i) => {
    res = await promise()
    console.log(i)
})