应该也是个比较经典的问题了,在实际项目中也经常会用到并发数量的控制,题目的要求是这样的:
实现一个批量请求函数 concurrentRequest(urls, maxNum),要求如下:
• 要求最大并发数 maxNum
• 每当有一个请求返回,继续进行下一个请求
• 所有请求完成后,结果按照 urls 里面的顺序依次打出
说到请求并发的话,第一个想到的应该就是Promise.all(),接受一个promise的list,等到list中的promise全部resolve之后,Promise.all()的返回值resolve结果数组,如果有一个promise变成reject状态,那么Promise.all()的返回值会reject
回到并发控制这道题上来,分析下思路,JS中的异步并发,可以通过同时执行多个异步请求来实现。而第二点:每当有一个请求返回,继续下一个请求,这个也很容易理解,promise天然就支持链式操作。
所以前两点总结起来就是:同时进行多个请求,每个请求fulfill之后,继续进行下一个请求,这样就实现了给定并发数量的情况下实现批量请求。
相对难理解的是第三点,怎么才能做到不同调用链并发时,还要保证请求得到的数据的顺序,思路是这样的, 我们现在有url的顺序,也就是urls这个数组,我们发起请求的顺序要按照这个数组的来,所以需要记录下当前请求到的url的位置,每次请求时位置都递增,请求完成时将这个将结果放在这个位置。
具体看一下代码吧:
function concurrentRequest(urls, max) {
// 初始状态的结果list
const res = new Array(res.length).fill("unfulfilled");
// 记录当前的请求到的index
let index = 0;
return new Promise((resolve, reject) => {
// urls数组的请求还没结束
function next(i) {
if (i < res.length) performFetch();
}
// 做一个max数量的并发
while (index !== max) {
performFetch();
}
function performFetch() {
const current = index++;
// 请求结束,将res数组作为结果resolve
if (current >= res.length) {
!!res.includes("unfulfilled") && resolve(res);
return;
}
const url = urls[current];
fetch(url)
.then((res) => {
res[current] = res;
next(current);
})
.catch((res) => {
// 错误也一并返回
res[current] = res;
next(current);
});
}
});
}
最终返回的是一个promise,promise的回调中会不断递归执行请求,直到urls对应的请求全部resolve为止,代码可能需要多看两遍才能理解