JS

JS并发请求数控制

Posted by Youzi on January 12, 2021

JS 并发请求数控制

掘金看到的文章,觉得不错,搜了些其他的方法也贴过来做个备忘;原文 1:https://juejin.cn/post/6916317088521027598,原文 2:https://juejin.cn/post/6844903972776460301

要求

  • 实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:
  • 要求最大并发数 maxNum
  • 每当有一个请求返回,就留下一个空位,可以增加新的请求
  • 所有请求完成后,结果按照 urls 里面的顺序依次打出

应用场景也很明确,请求数较多,但是又不可能一次性全部发送,所以需要控制最大并发数;

思路

一看到这个其实我想到的就是用Promise.all(),每次调用固定数量的请求方法,但是不能在请求完成后替换新的,因为这个方法会等到所有promise都改变状态了才会调用then,所以不能完成第三个需求;这里第二篇文章给了新的思路,用Promise.race()来触发是否有promise对象已经改变状态,依据此来替换队列里面的已经执行完了的promise对象,所以我们先用这个思路写写。

思路 1,Promise.all && Promise.race

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const multiRequest = async (urls, maxNum) => {
  // 请求函数返回的promise放在res里
  let res = [];
  // 每次发送请求的队列数组
  let limitRes = [];
  for (const url of urls) {
    // 请求返回的promise函数
    let p = fetch(url);
    res.push(p);
    p.finally(() => {
      // 详细解释看下文
      limitRes.splice(limitRes.indexOf(p), 1);
    });
    limitRes.push(p);
    while (limitRes.length >= maxNum) {
      // 当队列长度超过最大请求数时,就调用race,等待请求至少有一个改变了状态
      await Promise.race(limitRes);
    }
  }
  return Promise.all(res);
};
  • 注释:当 p 的状态改变时,我们也不去判断 p 的具体状态,而是直接从队列里移除掉;
  • 由于我们无从得知调用 race 后,到底是那个 promise 对象的状态改变了,所以我们在 p 这个对象上添加了 finally 方法,在 limitRes 队列里找到这个 promise 对象并移除;
  • 最后我们在 limitRes 队列里添加了这个对象。

思路 2, 手动实现

思路 2,不借助Promise.all方法,而是手动去递归调用执行函数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const multiRequest = async (urls, maxNum) => {
  // 请求总数
  const len = urls.length;
  // 保存结果
  const result = new Array(len).fill(false);
  // 请求完成的总数量
  let count = 0;
  return new Promise((res, rej) => {
    // 初始化时,调用next函数maxNum次
    while (count < maxNum) {
      next();
    }
    // next函数定义
    const next = () => {
      // 当前result数组下标
      let current = count;
      // 完成总数量自增
      count++;
      // 当前下标已经超过请求总数了,说明已经请求完了,就调用resolve函数然后退出
      if (current >= len) {
        res(result);
        return;
      }
      let url = urls[current];
      fetch(url)
        .then(res => {
          // 保存请求的结果
          result[current] = res;
        })
        .catch(err => {
          result[current] = err;
        })
        .finally(() => {
          // 判断请求是否全部完成
          if (current < len) {
            // 注意这里的递归,当请求没有全部完成时,就调用next函数继续请求
            next();
          }
        });
    };
  });
};

整体思路很简单了,就是在初始化的,调用maxNum次实际执行函数,这样就发了maxNum个请求,然后在函数体内的promise方法里面,在finally时去判断是否全都请求完了,没请求完就接着递归,等于说是当某个请求完成了就继续请求下一个;

小总结

思路肯定不止这两种咯,还有一些第三方库也自带这些函数,推荐两个,RxJS.mergeMapasync.eachLimit