前言 📝

在前端进行并发请求的时候,前端浏览器为了减轻服务器的压力,限制同一域名下并发请求的最大数量是6~8个(每个浏览器的最大并发数量不一致)。既然浏览器帮助我们控制了最大并发量,那为什么还要手动控制并发呢?原因很简单,因为面试官会问。其实还有一点就是,浏览器默认的等待机制是全局的,无法做到更细粒度的控制。我们手写控制并发的情况下,自己可以实现不同的排队策略、错误处理策略、超时处理策略、控制最大并发数量等。

如下所示,定义一个 result 用来缓存请求队列返回的结果,定义一个参数 index 记录下一次请求的索引。然后我们按照并发量去循环发送网络请求。在循环请求的时候,finally中判断是否继续发送请求还是终止请求返回所有结果。

注意:result存储的是返回结果,但是并发多个请求返回的顺序并不一致,所以在fetchApi函数内定了i,来记录当前网络请求的索引。用于将网络请求的返回结果存储在对应的位置。

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
43
44
45
  <script>
    const requestQueue = (urls = [], maxNum = 6) => {
      return new Promise((resolve, reject) => {
        if(!urls.length) {
          resolve([])
          return
        }

        const result = []
        let index = 0

        async function fetchApi() {
          const i = index
          const url = urls[i]
          index++

          try {
            const resp = await fetch(url)
            const res = await resp.json()
            result[i] = res
          } catch(err) {
            result[i] = err
          } finally {
            if(result.length === urls.length) {
              resolve(result)
              return
            }
           
            if(index < urls.length) {
              fetchApi()
            }
          }
        }

        const count = Math.min(maxNum, urls.length)
        for(let i =  0; i < count; i++) {
          fetchApi()
        }
      })
    }

// 测试代码
    const urls = new Array(8).fill('http://127.0.0.1:3000')
    requestQueue(urls, 4).then(res => console.log(res))
  </script>

我们简单用node写一个服务端的代码,测试一下我们的函数有没有问题:

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
// server.js
const http = require('http');
const url = require('url');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const pathname = parsedUrl.pathname;
  const method = req.method;

  res.setHeader('Content-Type', 'application/json');
  res.setHeader('Access-Control-Allow-Origin', '*');

  if (method === 'GET' && pathname === '/') {
    res.statusCode = 200;
    console.log('Have connect: ' + parsedUrl.query.index);
    setTimeout(() => {
      res.end(JSON.stringify({ message: 'Hello World!' }));
    }, 6000)
  } else if (method === 'POST' && pathname === '/data') {
    let body = '';
    req.on('data', chunk => {
      body += chunk.toString();
    });
    req.on('end', () => {
      const data = JSON.parse(body);
      res.statusCode = 200;
      res.end(JSON.stringify({ message: `You sent: ${JSON.stringify(data)}` }));
    });
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ message: 'Not Found' }));
  }
});

server.listen(port, hostname, () => {
  console.log(`Server is running at http://${hostname}:${port}/`);
});

我这里并发数量改成6,接口换成100个试一下:

1
2
const urls = new Array(100).fill('http://127.0.0.1:3000')
requestQueue(urls, 6).then(res => console.log(res))

image.png

我们可以看到和我们的预期一样,一次发送6个请求。正好发送了一百个网络请求,最后将返回值按照发送请求的顺序对应的放到数组里面全部返回。

image.png

image.png