并发请求+数量控制

281 阅读3分钟

在掘金潜水🤿一年多了,昨晚突发奇想,大概实现了控制并发

常见的场景题之一。 对于这个场景应该如何去设置?

我的想法是这样子。 首先取出控制数量个数的请求,然后并发的开启任务,等待任务完成之后(无论成功还是失败,然后添加下一个任务) 同时我们需要保证发起请求的顺序与得到响应的顺序是一致的。

需要拿到下标这样子,我们通过 res来存储promise,以便查看得到的各个请求的下标是否对应之前的promise

为什么这样子,因为在实际中可能出现很多请求,比如大文件分片上传,就需要确认哪一个分片对应哪一个索引,否则等上传成功之后就无法成功拼接出正确的文件

如何实现完成一个就取出来呢,这里可以使用map存储promise和其下标的关系

let pool = new Map()

且因为我们完成之后就要取出来,因此就可以删除掉promise索引

方向明确现在开搞

async function async_pool_Promise(PromiseList,limit){
	const results = []//存放结果的
	const pool = new Map()//存放promise与其索引关系的。

} 

这里我们采用什么来拿到各个promise。 递归?递归不可中断,且不太可控。且多次递归会使得调用栈极其臃肿,因此尽可能避免 因此我们采用循环的方式来控制 如何一个数组确定最快完成的,无论成功或者失败——Promise.race()

PS:这里可能有小伙伴不知道Promise.race(),这个方法是传入一个promise数组,返回最快完成的那一个,无论成功还是失败

for(let i = 0; i <= PromiseList; i++){
	if(pool.size > limit){
		await Promise.race(pool.keys())
	}
}

为什么这里需要这样 我们可以这样理解

Pasted image 20240403000523.png

这时候就需要最快那一个出现啦

Pasted image 20240403000653.png 这样就完成了一次更替 当后者进入pool时,又满足if条件,又进入等待

那么对于一个promise完成之后要干嘛? 是不是要将结果添加到results中,然后将自己在pool中的痕迹清空。

如何实现自动? promise有then方法 通过promise的then方法,注册事件实现完成后自动赋值,以便确认哪个promise对应哪一个序号

const cb = (value,reason)=>{
	if (reason) {
                res[pool.get(promise)] = { status: 'rejected', reason };
            }else{
                res[pool.get(promise)] = { status: 'fulfilled', value };
            }
            pool.delete(promise)
}
promise.then(value=>cb(value),reason=>cb(null,reason))

这样当失败时添加一个对象,成功时添加一个对象

那么大概代码就成功了

async function async_pool_Promise2(requestList, limits) {
    const res = []
    const promises = []
    const pool = new Map()//为了存储索引
    for (let i = 0; i < requestList.length; i++) {
        if (pool.size >= limits) {
            await Promise.race(pool.keys()).catch(err => err)
        }
        const promise = requestList[i]
        const cb = (value,reason) => {
            if (reason) {
                res[pool.get(promise)] = { status: 'rejected', reason };
            }else{
                res[pool.get(promise)] = { status: 'fulfilled', value };
            }
            pool.delete(promise)
        }
        promise.then(value=>cb(value), reason=>cb(null,reason))
        pool.set(promise, i)
        promises.push(promise)
    }
	return res;
}

这样子对吗? 其实还有些欠缺

比如最后pool池子的还没结束诶

Pasted image 20240403001743.png

那么等待池子中的所有promise完成可以使用 Promise.allSettled(pool)

PS:Promise.allSettled(),是传入一个promise数组,然后当这个promise数组中的所有promise的状态都改变

才结束。为什么这里不使用Promise.all()? 因为Promise.all()是如果有一个失败就会结束,那么其他的promise状态就可能未知

那么完整代码如下

async function async_pool_Promise2(requestList, limits) {
    const res = []
    const pool = new Map()//为了存储索引
    for (let i = 0; i < requestList.length; i++) {
        if (pool.size >= limits) {
            await Promise.race(pool.keys()).catch(err => err)
        }
        const promise = requestList[i]
        const cb = (value,reason) => {
            if (reason) {
                res[pool.get(promise)] = { status: 'rejected', reason };
            }else{
                res[pool.get(promise)] = { status: 'fulfilled', value };
            }
            pool.delete(promise)
        }
        promise.then(value=>cb(value), reason=>cb(null,reason))
        pool.set(promise, i)
    }
    await Promise.allSettled(pool.keys())//剩下的交给allSettled处理
    return res;
 }

async_pool_Promise2(promises, 5).then(results => {
   console.log(results);
 })

欢迎👏各位大佬斧正🙏