并发请求

150 阅读4分钟

前言

此题为如何处理并发请求,意思就是让你完成一个函数,传入一个url地址的数组,再传入一个最大的并发数,去完成请求。

图解

一个url数组,里面的每一项都是一个url地址,然后给一个最大的并发数maxNum,例如给我最大的并发数是3(maxNum=3),在发送请求的时候,可以同时最多发送3个请求。当请求C完成/结束了,现在的并发数只剩下AB了,然后用后面的D来补位,直到把所有的请求都发出,最后将每一个请求的结果都放到一个数组里面去(无论成功还是失败,失败将错误信息存入数组),而数组的顺序要与url地址的顺序保持一致。最后将这个数组返回。

并发-urls.png

并发-最多3个.png

并发-C完成.png

并发-存到数组里.png

需求分析

异步请求

函数肯定是返回一个promise,而且这个promise它的状态肯定是完成的。不可能是拒绝状态

 function concurRequest(urls,maxNum){
    return  new Promise(resolve=>{
    })
 }

特殊情况

上面图可以看出,要将每个请求的结果都存入数组中。先排除特殊情况,当传入的数组为空,此时就不用请求

 function concurRequest(urls,maxNum){
    return  new Promise(resolve=>{
      // 特殊情况:当url数组为空,不发请求
       if (urls.length===0) {
        resolve([])
         return;
       }
       const results =[];
    })
 }

正常情况

题目的意思,无非就是从url数组中依次取出元素来进行发送请求,当某个请求完成了再取出来发送一个请求,以此类推。在这一循环中有一个重复的动作:取出url数组的某一项,然后发送请求。那此时我们可以定义一个变量index,就是这个变量它指着哪一个下标,我就取哪一项,当取完这一项后,变量index加1,继续往后取

function concurRequest(urls, maxNum) {
  return new Promise(resolve => {
    // 特殊情况:当url数组为空,不发请求
    if (urls.length === 0) {
      resolve([])
      return;
    }
    const results = [];
    let index = 0;//下一次请求的下标
    //  发送请求函数
    function request() {
      const url = urls[index]; //发送请求地址
      index++;
      console.log(url);
      fetch(url)
    }
    // 模仿五次请求
    request();
    request();
    request();
    request();
    request();
  })
}

准备数据

  <script>
  //  模拟 将urls存放100个url地址,最高并发数5个
   const urls=[];
   for(let i =0;i<=100;++i){
    urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`)
   }
   concurRequest(urls,5).then((res)=>{
    console.log(res);
   })
  </script>

输出结果

并发-输出结果.png

由结果可以看出,已经发送了5次请求,就是把url数组里边前五个都拿出来了,发送出去了,那接下来要做的就是将这个请求的结果加入到结果集里面,而且无论成功还是错误都要加入这个结果集里面,那么怎么加入呢?push行吗?答案是不行,因为push有可能会改变它的顺序(结果集),为什么?因为我们不知道哪个请求会先完成。那怎么实行呢? 我们可以通过发送到哪个请求的时候,它所在的下标,然后我用对应的下标将其放到该下标位置,那下标是谁呢?变量index? 很显然不是,因为index在不断的往后变动,例如,当C完成的时候,index早就不在这个位置了,那怎么办呢?最简单的就是将当前的index保存起来

  //request函数修改
  async function request() {
      const i = index; //存当前index
      const url = urls[index]; //发送请求地址
      index++;
      try {
        const resp = await fetch(url);
        results[i] = resp
      } catch (err) {
        results[i] = err
      }
      console.log(results);
    }

输出结果

并发-请求结果2.png

可以看出,A先完成,其他是空的,然后又有两个完成,中间两个是空的,

并发-结果集顺序.png

而且可以看出,结果集的顺序跟请求顺序是一样的,接下来要做的呢,就是当这个请求完成了,要继续发送下一个请求了。

async function request() {
      const i = index; //存当前index
      const url = urls[index]; //发送请求地址
      index++;
      console.log(url);
      try {
        const resp = await fetch(url);
        results[i] = resp
      } catch (err) {
        results[i] = err
      } finally {
        //  完成之后发送下一个请求
        request()
      }

    }

并发-请求结束继续发送请求.png

由输出结果看出,这个并发会一直执行下去,但是此时超过了url地址,它还是会继续发送请求,这时我们就要做出限制了

并发-请求后index继续增加.png

做限制之后,我们要考虑一下它怎么调用呢,调用时还要考虑一个问题,如果传入的url数组长度比最大并发数小呢?那也要做一层限制,最终结果如下

function concurRequest(urls, maxNum) {
  return new Promise(resolve => {
    // 特殊情况:当url数组为空,不发请求
    if (urls.length === 0) {
      resolve([])
      return;
    }
    const results = [];
    let index = 0;//下一次请求的下标
    let count = 0;//请求完成的数量
    //  发送请求函数
    async function request() {
      // 当index等于数组长度时结束
      if (index === urls.length) {
        return
      }
      const i = index; //存当前index
      const url = urls[index]; //发送请求地址
      index++;
      try {
        const resp = await fetch(url);
        results[i] = resp
      } catch (err) {
        results[i] = err
      } finally {
        // 判断是否所有请求都完成了
        count++;
        if (count === urls.length) {
          console.log("请求完了");
          resolve(results)
        }
        //  完成之后发送下一个请求
        request()
      }
    }
    // 防止最大并发数小于url长度的错误
    const timers = Math.min(maxNum, urls.length)
    for (let i = 0; i < timers; i++) {
      request();
    }
  })
}

输出结果

并发-最终结果.png

结尾

本题到这就结束了,上面有什么不足之处,希望各位前端大佬指出