弑君者
2022/04/25阅读:35主题:默认主题
头条的经典算法题-批量请求数据,并控制请求并发数量
实现批量请求数据,并控制请求并发数量,最后所有请求结束之后,执行callback回调函数
题目描述:
实现如下函数,可以批量请求数据,所有的url在urls数组里边,同时可以通过max参数控制并发的最大请求数量,当所有的请求结束之后,执行callback函数
function sendRequest(urls: string[], max:number,callback: () => void)
解析:
题目本身的算法思想不是很难,其实就是同时并发多个请求,当其中有一个请求结束之后,再重新发送一个请求,等到所有的请求都结束之后,把结果传入到callback函数里边执行就可以了。
既然该题目的实现逻辑很简单,那么问题就来了,可以自己去写写试试,看看自己能不能完整的实现。
如果不能实现,其实也没啥,关键是找到自己的问题在哪里?然后思考下在已知和结果之间的路自己为啥没有想到😁。
我当时思考的时候,就是卡在如何在一个请求结束之后,重新发起一个请求,同时把该请求的结果放入到结果数组中?
这个无限的不断的请求,应该很容易想到使用递归。
我的想法是首先发送多个请求,当其中一个请求结束之后,在Promise的then方法里边重新发起请求。
但是这里有个问题,就是我们不知道当前返回的结果是对应哪个url的结果,就像fetchSingleUrl注释的那样。
const allRequests = [
"http://jsonplaceholder.typicode.com/posts/1",
"http://jsonplaceholder.typicode.com/posts/2",
"http://jsonplaceholder.typicode.com/posts/3",
"http://jsonplaceholder.typicode.com/posts/4",
"http://jsonplaceholder.typicode.com/posts/5",
"http://jsonplaceholder.typicode.com/posts/6",
];
function sendRequest(urls,max,callback) {
const urlsLen = urls.length;
const promiseAll = [];
// 最后所有的结果
const res = new Array(urlsLen).fill(0);
let curIndex = -1;
function fetchSingleUrl(curRes) {
// 这里获取到的curIndex其实是错误的,无法确定当前返回的结果是该url获取的
console.log(999,curIndex)
res[curIndex] = curRes;
curIndex++;
if(curIndex >= urlsLen) {
callback(res);
}else {
fetch(urls[curIndex]).then(fetchSingleUrl).catch(fetchSingleUrl);
}
}
// 如果urls总共比max还小,那么就使用Promise.all一起发出去
if(urlsLen <= max) {
for(let i =0;i<urlsLen;i++) {
promiseAll.push(fetch(urls[i]));
}
Promise.all(promiseAll).then(curRes => {
console.log(112,curRes)
callback(curRes);
}).catch((err)=> {
console.log(333,err)
callback(err);
})
}else {
for(let i =0;i<max;i++) {
fetch(urls[i]).then(fetchSingleUrl).catch(fetchSingleUrl)
}
}
}
sendRequest(allRequests, 2, (result) => console.log(result));
上面的方法可以解决一部分问题,但是无法解决在返回的结果数组中,每个数组的结果是对应url的结果。
那有没有其它方法?
我们可以想下如果我们想让一个函数不执行,也就是等待状态改变的时候,我们在js中可以使用什么?
就是我们可以使用Promise,我们可以把一些这样的函数放入到一个队列中,当我们想要它运行的时候,我们取出来,然后把Promise的状态给改成已经完成状态,那么是不是就可以发送请求了。
最关键的是这段代码,这里会把超过最大请求数的请求,在这里使用Promise给holding住,同时把改变状态的resolve函数 push到一个数组中,一直等到有其它请求结束之后,从数组中拿出resolve函数,改变状态后,再执行下去
// 这个index传过来就是为了对应好哪个请求,
// 放在对应的results数组对应位置上的,保持顺序
async function request(index, reqUrl) {
// 阻塞队列增加一个 Pending 状态的 Promise,
// 这里最关键,就是如果当前发送的请求已经超过最大请求数了,在这里holding住,等待状态的改变后,再执行
if (currentReqNumbers >= max) {
await new Promise((resolve) => blockQueue.push(resolve));
}
reqHandler(index, reqUrl);
}
完整代码如下:
function sendRequest(urls, max, callbackFunc) {
const urlsLen = urls.length;
// 等待排队的那个队列
const blockQueue = [];
// 现在请求的数量,表示现在已经发送了几个请求
let currentReqNumbers = 0;
// 发送并且有返回的请求数量
let requestsDoneNumbers = 0;
// 存储最后的结果
const results = new Array(urlsLen).fill(0);
// 如果urls总共比max还小,那么就使用Promise.all一起发出去
if(urlsLen <= max) {
const promiseAll = [];
for(let i = 0;i<urlsLen;i++) {
promiseAll.push(fetch(urls[i]));
}
Promise.all(promiseAll).then(curRes => {
callbackFunc(curRes);
}).catch((err)=> {
callbackFunc(err);
})
}else {
init();
}
async function init() {
// 开始发送请求,如果超过最大max
for (let i = 0; i < urlsLen; i++) {
request(i, urls[i]);
}
}
// 这个index传过来就是为了对应好哪个请求,
// 放在对应的results数组对应位置上的,保持顺序
async function request(index, reqUrl) {
// 阻塞队列增加一个 Pending 状态的 Promise,
// 这里最关键,就是如果当前发送的请求已经超过最大请求数了,在这里holding住,等待状态的改变后,再执行
if (currentReqNumbers >= max) {
await new Promise((resolve) => blockQueue.push(resolve));
}
reqHandler(index, reqUrl);
}
async function reqHandler(index, reqUrl) {
currentReqNumbers++;
console.log("现在i是" + index + " 正请求:");
try {
const result = await fetch(reqUrl);
results[index] = result;
} catch (err) {
results[index] = err;
} finally {
currentReqNumbers--;
requestsDoneNumbers++;
console.log(`${index}请求结束`);
if (blockQueue.length>0) {
// 每完成一个就从阻塞队列里剔除一个
// 将最先进入阻塞队列的 Promise 从 Pending 变为 Fulfilled
blockQueue[0]();
blockQueue.shift();
console.log(
"消灭一个blockQueue第一个阻塞后,排队数为 : " + blockQueue.length
);
}
if (requestsDoneNumbers >= urlsLen) {
callbackFunc(results);
}
}
}
}
const allRequests = [
"http://jsonplaceholder.typicode.com/posts/1",
"http://jsonplaceholder.typicode.com/posts/2",
"http://jsonplaceholder.typicode.com/posts/3",
"http://jsonplaceholder.typicode.com/posts/4",
"http://jsonplaceholder.typicode.com/posts/5",
"http://jsonplaceholder.typicode.com/posts/6",
];
sendRequest(allRequests, 7, (result) => console.log(result));
总结:
在试用Promise的时候,可以把当前状态holding住,同时还可以把改变状态的函数给抛出去,等到合适的时机再执行
参考
https://blog.csdn.net/weixin_42655717/article/details/118862407
作者介绍