JS 并发请求数控制
掘金看到的文章,觉得不错,搜了些其他的方法也贴过来做个备忘;原文 1:https://juejin.cn/post/6916317088521027598,原文 2:https://juejin.cn/post/6844903972776460301;
要求
- 实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:
- 要求最大并发数 maxNum
- 每当有一个请求返回,就留下一个空位,可以增加新的请求
- 所有请求完成后,结果按照 urls 里面的顺序依次打出
应用场景也很明确,请求数较多,但是又不可能一次性全部发送,所以需要控制最大并发数;
思路
一看到这个其实我想到的就是用Promise.all()
,每次调用固定数量的请求方法,但是不能在请求完成后替换新的,因为这个方法会等到所有promise
都改变状态了才会调用then
,所以不能完成第三个需求;这里第二篇文章给了新的思路,用Promise.race()
来触发是否有promise
对象已经改变状态,依据此来替换队列里面的已经执行完了的promise
对象,所以我们先用这个思路写写。
思路 1,Promise.all && Promise.race
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const multiRequest = async (urls, maxNum) => {
// 请求函数返回的promise放在res里
let res = [];
// 每次发送请求的队列数组
let limitRes = [];
for (const url of urls) {
// 请求返回的promise函数
let p = fetch(url);
res.push(p);
p.finally(() => {
// 详细解释看下文
limitRes.splice(limitRes.indexOf(p), 1);
});
limitRes.push(p);
while (limitRes.length >= maxNum) {
// 当队列长度超过最大请求数时,就调用race,等待请求至少有一个改变了状态
await Promise.race(limitRes);
}
}
return Promise.all(res);
};
- 注释:当 p 的状态改变时,我们也不去判断 p 的具体状态,而是直接从队列里移除掉;
- 由于我们无从得知调用 race 后,到底是那个 promise 对象的状态改变了,所以我们在 p 这个对象上添加了 finally 方法,在 limitRes 队列里找到这个 promise 对象并移除;
- 最后我们在 limitRes 队列里添加了这个对象。
思路 2, 手动实现
思路 2,不借助Promise.all
方法,而是手动去递归调用执行函数;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const multiRequest = async (urls, maxNum) => {
// 请求总数
const len = urls.length;
// 保存结果
const result = new Array(len).fill(false);
// 请求完成的总数量
let count = 0;
return new Promise((res, rej) => {
// 初始化时,调用next函数maxNum次
while (count < maxNum) {
next();
}
// next函数定义
const next = () => {
// 当前result数组下标
let current = count;
// 完成总数量自增
count++;
// 当前下标已经超过请求总数了,说明已经请求完了,就调用resolve函数然后退出
if (current >= len) {
res(result);
return;
}
let url = urls[current];
fetch(url)
.then(res => {
// 保存请求的结果
result[current] = res;
})
.catch(err => {
result[current] = err;
})
.finally(() => {
// 判断请求是否全部完成
if (current < len) {
// 注意这里的递归,当请求没有全部完成时,就调用next函数继续请求
next();
}
});
};
});
};
整体思路很简单了,就是在初始化的,调用maxNum
次实际执行函数,这样就发了maxNum
个请求,然后在函数体内的promise
方法里面,在finally
时去判断是否全都请求完了,没请求完就接着递归,等于说是当某个请求完成了就继续请求下一个;
小总结
思路肯定不止这两种咯,还有一些第三方库也自带这些函数,推荐两个,RxJS.mergeMap,async.eachLimit