浏览器对单个域名最多6个TCP连接的限制,那我们在JS层使用队列控制并发请求的数量有意义吗

43 阅读3分钟

答案是:有意义,而且非常重要。

浏览器对单个域名最多6个TCP连接的限制(HTTP/1.1规范)只是一个被动的、底层的网络层限制。而在JavaScript层用队列主动控制并发请求数量,是一个主动的、应用层的优化策略,两者目标不同,互为补充。

image.png

上图揭示了关键点:应用层的队列是网络层连接池的“上游”。即使浏览器只能同时发出6个请求,如果你的代码瞬间向队列中塞入1000个请求,浏览器依然会“疯狂地”尽快处理这6个,处理完一个立刻补下一个。这会导致:

  1. 关键请求被阻塞:所有请求在浏览器队列中是无序竞争,你的“登录”请求可能被排在100个图片请求之后。
  2. 客户端资源耗尽:浏览器内存、CPU会因瞬间处理大量回调而卡顿,页面可能无响应。
  3. 对服务器不友好:突发流量可能对服务器造成压力。

📝 主动控制请求队列的核心价值

因此,在JS层实现请求队列(通常使用队列或“令牌桶”算法,栈结构因后进先出不常用)的意义在于:

控制维度浏览器限制(被动)JS请求队列(主动)
核心目标避免网络拥塞,复用连接优化用户体验,保障应用稳定性
控制粒度域名级,所有请求一视同仁应用级,可区分请求类型、优先级
能否调度否,先进先出,可设置优先级(如登录 > 渲染数据 > 日志)
流量整形否,瞬时塞满6个连接,平滑流出,避免突发流量
错误处理,可统一重试、降级、上报

🛠️ 实践示例:实现一个简单的请求队列

下面是一个使用 Promise队列 实现并发控制的简化例子:

class RequestQueue {
  constructor(maxConcurrent = 3) { // 默认并发数可小于6
    this.maxConcurrent = maxConcurrent;
    this.queue = []; // 任务队列
    this.activeCount = 0; // 正在进行的请求数
  }

  // 添加请求到队列
  add(requestFn, priority = 0) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject, priority });
      // 可选:按优先级排序
      this.queue.sort((a, b) => b.priority - a.priority);
      this.run();
    });
  }

  // 执行队列
  run() {
    // 当队列未空且活跃数未达上限时
    while (this.activeCount < this.maxConcurrent && this.queue.length) {
      const task = this.queue.shift(); // 取队首任务
      this.activeCount++;
      
      task.requestFn()
        .then(task.resolve)
        .catch(task.reject)
        .finally(() => {
          this.activeCount--;
          this.run(); // 一个请求完成,尝试启动下一个
        });
    }
  }
}

// 使用示例
const queue = new RequestQueue(3); // 全局控制并发为3

// 模拟一个异步请求函数
function mockRequest(id, time = 1000) {
  return () => new Promise(resolve => {
    console.log(`请求 ${id} 开始`);
    setTimeout(() => resolve(`结果 ${id}`), time);
  });
}

// 添加100个请求,但最多同时只有3个在执行
for (let i = 0; i < 100; i++) {
  queue.add(mockRequest(i, Math.random() * 2000))
    .then(result => console.log(result));
}

💡 现代演进与最佳实践

  1. HTTP/2 :它们支持多路复用,一个连接即可并行处理大量请求,传统的“6连接”限制已不适用。但前端的主动队列控制依然必要,原因从“防止连接数超限”变为“防止客户端过载”和“业务优先级调度”。
  2. 浏览器原生支持:现代浏览器提供了 Resource Hints(如 preconnect, prefetch)和 Fetch APIpriority 提示,但控制粒度仍不如应用层队列。
  3. 实际应用
    • 关键路径优先:用户登录、首屏渲染所需数据应最高优先级。
    • 非关键请求延迟:埋点日志、非首屏图片等可放入空闲队列(requestIdleCallback)。
    • 统一管理:在Axios等请求库的拦截器中集成队列,对全站请求进行治理。