每天一个小知识,今日知识-如何设计一个并发请求控制函数

micbin / 2023-08-24 / 原文

假如给你一个数组,里面是请求路径,如何设计一个函数去控制这些请求的并发呢?

这里我们用的请求路径为https://jsonplaceholder.typicode.com/todos来模拟

      const reqArr = [];
      for (let i = 1; i <= 10; i++) {
        reqArr.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
      }

 

第一种方案:基于Promise实现:

首先定义一个函数,并且确定其形参,第一个为传入的请求路径数组,第二个为需要限制的并发数,并且返回值为Promise。

  function limitReqFn(urlArray, limitNum){
return new Promise(resolve)=>{}
}

 

 

第一步:首先我们需要对传入的路径数组判断一下是否为空。 

         if (urlArray.length === 0) {//如果为空就直接resolve出去
            resolve([]);
            return;
          }

  

第二步:我们需要定义一个result数组用于接收请求响应结果,并且定义当前请求路径的index值下标currentRequetIndex,以及一个异步请求函数request

     const result = [];
     let currentRequetIndex = 0;
     async function request(){}

 

第三部:完善request逻辑,核心逻辑就是拿到当前请求的路径并且进行网络请求,然后将结果放到result里,需要注意的就是拿到请求结果res后不能直接用push方法放到result里,因为返回的顺序可能不一致。 需要拿一个临时变量i存储currentRequetIndex,然后再将结果数组result的下标i的值设为请求返回的结果res

       async function request() {
            if (currentRequetIndex === urlArray.length) { //当索引下标等于传入的url数组时返回
              return;
            }
            const i = currentRequetIndex;//临时存储currentRequestIndex
            const currentReqUrl = urlArray[currentRequetIndex];//拿到当前请求路径
            currentRequetIndex++; //将currentRequestIndex进行+1,指向下一个请求路径
            try {
              const res = await fetch(currentReqUrl);
              result[i] = res;//将结果赋值给result数组
            } catch (error) {
              result[i] = error;
            } finally {//请求完后判断result的长度是否和传入的数组相同,然后返回结果
              if (result.length === urlArray.length) {
                resolve(result);
              }
              request();//将下一个路径拿来请求
            }
          }

 

第四部:设置并发数量,并且进行请求

       const times = Math.min(limitNum, urlArray.length);//将limitNum和传入的数组长度做一下比较
          for (let i = 0; i < times; i++) {
            request();
          }

 

完整代码:

      const reqArr = [];
      for (let i = 1; i <= 10; i++) {
        reqArr.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
      }
      function limitReqFn(urlArray, limitNum) {
        return new Promise((resolve) => {
          if (urlArray.length === 0) {
            resolve([]);
            return;
          }
          const result = [];
          let currentRequetIndex = 0;
          async function request() {
            if (currentRequetIndex === urlArray.length) {
              return;
            }
            const i = currentRequetIndex;
            const currentReqUrl = urlArray[currentRequetIndex];
            currentRequetIndex++;
            try {
              const res = await fetch(currentReqUrl);
              result[i] = res;
            } catch (error) {
              result[i] = error;
            } finally {
              if (result.length === urlArray.length) {
                resolve(result);
              }
              request();
            }
          }
          const times = Math.min(limitNum, urlArray.length);
          for (let i = 0; i < times; i++) {
            request();
          }
        });
      }
      limitReqFn(reqArr,2).then((data)=>console.log(data));


接下来我们到浏览器里去试一试,f12打开network,切换到fast 3G模拟慢网

 

  

第二种方案:采用数组令牌的方式。

第一步:我们根据传入的并发条数创建一个对应的token数组,在请求的时候,需要从数组中拿一个token作为标识,然后请求完成后将token归还回去,然后定义一个等待队列,一个执行队列。

    function limitRequest(rqArr, length) {
        const tokenKey = Array.from({ length }, (v, k) => `token${k}`);//创建token标识数组
        const backToken = function (token) { //归还token的方式
          tokenKey.push(token);
        };
        const getTokenKey = function () { //获取一个token
          return tokenKey.splice(0, 1)[0];
        };
        let waitQueen = [];//等待队列
        let exectuor = [];//执行队列
}

  

第二步:我们需要定义一个方法将对应的请求带上token存入执行队列,如果没拿到token的请求就放入等待队列。

 

       const generateFn = function (arr, clear=false) {
            if (clear) {// 清空队列 
              exectuor = [];
            }
            for (let i = 0; i < arr.length; i++) {
              if (tokenKey && tokenKey.length) {//如果还有token剩余
                const reqObj = {
                  token: getTokenKey(),
                  url: arr[i],
                };
                exectuor.push(reqObj);//将请求添加到执行队列
              } else {
                waitQueen.push(rqArr[i]);//没有拿到token就添加到等待队列
              }
            }
                              runExectuor();//执行队列生成完后,就到执行队列里去请求
          };

  

第三步:我们已经生成了执行队列,这个时候就需要定义一个执行队列里请求的方法。

    const runExectuor = function () {
            for (const fn of exectuor) {
              fetch(fn["url"]).then((v) => {
                backToken(fn.token);//执行完成一个请求后就归还token
                if (waitQueen && waitQueen.length) {//如果等待队列里有请求就拿出来放到执行队列里
                  generateFn(waitQueen.splice(0, 1), true);//每次生成需要清空队列
                }
              });
            }
          };

  

完整代码:

      function limitRequest(rqArr, length) {
        const tokenKey = Array.from({ length }, (v, k) => `token${k}`);
        const result = [];
        const backToken = function (token) {
          tokenKey.push(token);
        };
        const getTokenKey = function () {
          return tokenKey.splice(0, 1)[0];
        };
        let waitQueen = [];
        let exectuor = [];
          const generateFn = function (arr, type=false) {
            if (type) {
              exectuor = [];
            }
            for (let i = 0; i < arr.length; i++) {
              if (tokenKey && tokenKey.length) {
                const reqObj = {
                  token: getTokenKey(),
                  url: arr[i],
                };
                exectuor.push(reqObj);
              } else {
                waitQueen.push(rqArr[i]);
              }
            }
            runExectuor();
          };
          const runExectuor = function () {
            for (const fn of exectuor) {
              fetch(fn["url"]).then((v) => {
                backToken(fn['token']);
                if (waitQueen && waitQueen.length) {
                  generateFn(waitQueen.splice(0, 1), true);
                }
              });
            }
          };
          generateFn(rqArr);
      }
      const reqArr = [];
      for (let i = 1; i <= 10; i++) {
        reqArr.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
      }
      limitRequest(reqArr,2);

  

好啦,今天就分享这2种解决并发控制方法和思路。