弑君者

V1

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

分类:

前端

标签:

JavaScript

作者介绍

弑君者
V1