异步控制并发数

319 阅读3分钟

Promise基础

Promise.race 接受可迭代的对象,把状态设置为第一个 resolve 或 reject 的对象的状态,也就是返回的对象的状态由最快变化的 promise 对象决定,所以称为 race

但是注意,即便 race 返回的状态已经确定,其他的 promise 也会执行,并不是被吞掉了,利用这一点,可以帮助我们完成异步控制并发

async/await

已经不算新语法的两兄弟,首先 await 只能在 async 中使用,如果没有适用环境的话,可以用立即执行函数与其配合

await 后面的语句相当于 then 的回调,所以看起来是阻塞代码的,其实整体任务仍然是异步执行的,只是看起来很像同步代码

async 返回 promise 对象,如果返回值不为 promise,会调用 Promise.resolve 将其封装为 promise 对象再返回

比如说

const request=(time)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{resolve(time)},time)
    })
}
async function async1(){
    const p1=await request(1000)
    console.log(p1)
    const p2=await request(2000)
    console.log(p2)
    const p3=await request(1000)
    console.log(p3)
}
async1()

会按照秒数依次打印,看起来就像同步代码,而不是像 then 一样,需要分析执行顺序

异步控制并发

我们先思考一下,首先需要一个池子,来盛放并观察任务的数量,等数量达到限制时,我们需要执行任务,来释放空间,同时还需要加入任务,来控制后面任务的处理,我们先来试一下吧

const request=(time)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{resolve(time)},time)
    })
}
const asyncPool=(tasks,max)=>{
    const pool=[]
    for(let i=0;i<tasks.length;i++){
        const task=request(1000)
        pool.push(task)
        task.then(res=>{
            console.log(res)
            pool.splice(pool.indexOf(task),1)
        })
    }
}
const tasks = new Array(10).fill(0).map((v,i)=>i)
asyncPool(tasks,3)

image.png 很显然我们犯了异步编程的最简单的错误,for 循环在短时间内就能完成,效果就是 10 个 task 同时等待 then 中的回调,那么就会发生以上结果

所以我们必须阻塞一下代码的执行,我们首先可以使用 await 来做,那么什么时候阻塞呢,很容易想到当任务池子,也就是 pool 长度达到 max 时,我们需要执行异步任务,并释放空间,以便后续代码执行

修改如下

const request=(i)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{resolve(i)},1000)
    })
}
const asyncPool=async(tasks,max)=>{
    const pool=[]
    for(let i=0;i<tasks.length;i++){
        const task=request(i)
        pool.push(task)
        task.then(res=>{
            console.log(res,`当前并发数为${pool.length}`)
            pool.splice(pool.indexOf(task),1)
        })
        if(pool.length===max){
            await Promise.race(pool)
        }
    }
}
const tasks = new Array(10).fill(0).map((v,i)=>i)
asyncPool(tasks,3)

首先要加上 async

其次我们使用 Promsie.race 来释放任务池,从头开始分析,一直到 i 为 2 时,也就是 pool 里面已经存入了 3 个任务时,这时符合判断条件,用 await 去等待这三个任务,当然是第一个任务先 resolve,但是不代表其他两个任务也不执行

重要的是,只要第一个任务 resolve,也就是 Promise.race 返回的 promise 的状态变为 fullfilled,await 就会执行后续代码,也就是继续 for 循环,继续重复上述过程