2023-06-17 详解Promise面试题之最大请求并发数

184 阅读2分钟

前言:最近闲来无事,刷刷面试题。遇到了一道经典的promise并发的面试题,并写出解题思路分享以下

题目如下:

/**
 * 最大请求并发数,返回Promise
 * @param {string[]} urls 请求urls数组
 * @param {number} maxNum 最大请求数量
 * @returns {Promise<any[]>} Promise
 */
function concurRequest(urls, maxNum) {} // 完成函数主体部分

这道题主要是考察对promise的理解深度

弄明白函数的输入参数和输出如下图:

image.png

输入是urls字符串数组和最大并发数maxNum

输出是一个异步的promise,这个promise只有一个resolve,因为要等待所有的urls结束(成功或失败),所以要有个数组results保存每个请求结束的结果

function concurRequest(urls, maxNum) {
  return new Promise(resolve => {
    // 当urls数组为0时直接结束
    if (urls.length === 0) {
      resolve([]);
      return;
    }
    const results = []; // 存储每个请求的结果
  });
}

这里先判断下边界情况之一,当urls数组为空,没必要发请求了,直接返回空结果

然后定义发请求函数request

function concurRequest(urls, maxNum) {
  return new Promise(resolve => {
    // 当urls数组为0时直接结束
    if (urls.length === 0) {
      resolve([]);
      return;
    }
    const results = []; // 存储每个请求的结果
    let index = 0; // 下一个请求的下标

    // 发出请求
    async function request() {
      const i = index; // 记录当前发出请求的下标
      const url = urls[index];
      index++; // 每发出一个请求,下标加1
      console.log(url);

      try {
        const value = await fetch(url);
        results[i] = value;
      } catch (error) {
        results[i] = error;
      } finally {
        request(); // 每个请求结束后继续发请求
      }
    }

    // 测试
    request();
    request();
    request();
  });
}

这里写一个html文件,注册下urls数组如下:

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试</title>
  </head>
  <body>
    <script src="./concurRequest.js"></script>
    <script>
      const urls = [];
      for (let i = 1; i < 100; i++) {
        urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
      }
      concurRequest(urls, 20).then(resp => {
        console.log(resp);
      });
    </script>
  </body>
</html>

由于测试没用到maxNum,可以看到:

image.png

这里请求超出100后还在发出,是因为没有做继续发出请求限制条件,加上即可

async function request() {
  // 当请求等于urls数组长度,结束不在继续发请求了
  if(index===urls.length){
    return;
  }
...

到此,发送请求函数完成差不多了,不过还没有对返回的promise的状态做定论,这个状态肯定是resolved,也就是不管请求成功还是失败,结果都应该存储在results数组中,并且下标对应,然后等到全部请求都结束在resolve(results),如下:

async function request() {
      // 当请求等于urls数组长度,结束不在继续发请求了
      if (index === urls.length) {
        return;
      }

      const i = index; // 记录当前发出请求的下标
      const url = urls[index];
      index++; // 每发出一个请求,下标加1

      try {
        const value = await fetch(url);
        results[i] = value;
      } catch (error) {
        results[i] = error;
      } finally {
        count++;
        // 当完成请求的数量等于urls的长度时,结束
        if (count === urls.length) {
          resolve(results);
        }
        request(); // 每个请求结束后继续发请求
      }
    }

最后就是同时并发多个请求,并发数是urls的长度与maxNum的最小值,然后for循环运行request,这里考虑到for循环里的多个请求发出不是同时的,由于请求发出是异步的,认为之间差异时间很小,所以算是同时发出多个请求

// 取urls的长度和与maxNum的最小値,发出请求
    const times = Math.min(maxNum, urls.length);
    for (let i = 0; i < times; i++) {
      request();
    }

测试截图如下,这里设置的maxNum为3

可以把网络速度调慢点儿更明显

image.png

至此,完成了这道题目,总的来说还是加深了对promise的理解,完整代码如下:

test.html文件

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试</title>
  </head>
  <body>
    <script src="./concurRequest.js"></script>
    <script>
      const urls = [];
      for (let i = 1; i < 10; i++) {
        urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
      }
      concurRequest(urls, 3).then(resp => {
        console.log(resp);
      });
    </script>
  </body>
</html>

concurRequest.js文件

/**
 * 最大请求并发数,返回Promise
 * @param {string[]} urls 请求urls数组
 * @param {number} maxNum 最大请求数量
 * @returns {Promise<any[]>} Promise
 */
function concurRequest(urls, maxNum) {
  return new Promise(resolve => {
    // 当urls数组为0时直接结束
    if (urls.length === 0) {
      resolve([]);
      return;
    }
    const results = []; // 存储每个请求的结果
    let index = 0; // 下一个请求的下标
    let count = 0; // 请求完成的数量,包括成功和失败

    // 发出请求
    async function request() {
      // 当请求等于urls数组长度,结束不在继续发请求了
      if (index === urls.length) {
        return;
      }

      const i = index; // 记录当前发出请求的下标
      const url = urls[index];
      index++; // 每发出一个请求,下标加1

      try {
        const value = await fetch(url);
        results[i] = value;
      } catch (error) {
        results[i] = error;
      } finally {
        count++;
        // 当完成请求的数量等于urls的长度时,结束
        if (count === urls.length) {
          resolve(results);
        }
        request(); // 每个请求结束后继续发请求
      }
    }

    // 取urls的长度和与maxNum的最小値,发出请求
    const times = Math.min(maxNum, urls.length);
    for (let i = 0; i < times; i++) {
      request();
    }
  });
}