前言:Promise如何实现并发控制呢?有很多张图片,我们如何5张5张获取呢。
简单实现
总体思路是这样的:我们每次通过Promise.all请求并且返回limit个数的数据,使用async 和 await来阻塞代码。
可以看到,虽然实现了并发控制,但是这个的效果其实很差,虽然现在看上去没什么问题,但是如果当你并发请求中某个请求时间非常长的话,这个Promise.all会被await阻塞掉,体验非常差。
优化
我们能不能用另一种方式来实现并发控制,就是如果你当前的请求结束了,就新增一个新的请求进来,并且总共正在执行的并发请求不超过limit个。答案是肯定可以的。为了实现响应时间不同的请求,我们用timeout来模拟。可以看到,下面分别是3个1000ms的请求和1个2000ms的请求。
我们来模拟一下,Promise.all的场景,可以看到,必须得等第一次的两个任务执行完了才会接着执行,如果第二个任务是等待一年,岂不是寄了= =。
优化的并发控制的思路:我们将正在执行的异步任务保存在一个数组中,等到这个异步任务结束的时候从数组中移除。如果这个正在执行的异步任务数组的达到了limit限制,就用async+Promise.race阻塞,等到里面有任务完成之后才能继续遍历数组执行请求。
async function asyncPool(limit, arr, fn) {
const ret = []; //保存所有异步任务
const executing = []; //保存正在执行的异步任务
for (const item of arr) {
const p = fn(item); //异步请求
ret.push(p); // 将异步请求存入ret中
// 限制长度小于arr时需要并发控制
if (limit <= arr.length) {
// 为p指定then回调,当成功的时候从executing中移除
const e = p.then(
() => executing.splice(executing.indexOf(e), 1),
(err) => err
);
// 加入到正在执行的数组中
executing.push(e);
//达到限制,开始并发控制,await阻塞代码
//只有等到正在执行的里面结束了一个之后才可以继续循环开始请求
if (executing.length === limit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret).catch((err) => err);
}
//模拟请求
const timeout = (i) =>
new Promise((resolve) => {
console.log(i);
setTimeout(resolve, i, i);
});
asyncPool(
2,
[1000, 2000, 1000, 1000],
timeout
).then((res) => {
console.log(res);
});
效果如下