JavaScript中怎么并行发送多个HTTP请求并等待结果

作者:半夏时间:2026-11-03 12:12:53浏览:167

详细讲解在 JavaScript 中如何利用 Promise.all 与 Promise.allSettled 并行发起多个 HTTP 请求并等待结果,包含双阶段解析、错误熔断与容错降级、超时控制及并发池限流机制。

异步编程 Promise 网络请求 技术指南 · 实用教程
JavaScript中怎么并行发送多个HTTP请求并等待结果
在 JavaScript 中并行发送多个 HTTP 请求并等待全部结果,核心是借助 ES6 提供的 Promise.all 或 Promise.allSettled 方法,配合原生 fetch 与 Array.prototype.map。具体做法是将需要请求的 URL 数组映射为一组立即执行并处于 Pending 状态的 Promise,随后使用 await Promise.all(promises) 同步等待所有请求返回并解析数据。对于需要容忍部分接口报错的场景,改用 Promise.allSettled 即可获取每个请求的具体状态;若涉及数十甚至上百个并发连接,则应通过并发池限制同时处于连接状态的数量,防止击垮服务或触发布局限流。

一、为什么需要并行请求:对比顺序阻塞执行

在处理数据可视化看板、聚合首页或批量文件上传等场景时,页面往往需要同时请求多个不同微服务或端点的数据。初学者容易误在数组循环中直接使用 await,例如采用 for...of 循环逐个请求,这种写法会将网络请求退化为串行执行。

在串行模式下,后一个请求必须等待前一个请求完成 TCP 握手、服务端处理并完整传输响应体后才能启动,所有请求的等待时间呈线性累加。而现代浏览器与 Node.js 具备高效的异步 I/O 能力,借助 Promise 可以让所有 HTTP 请求在同一时刻全部触发,浏览器底层的网络线程池会并行调度并发送它们。整个操作的完成耗时不再是各请求耗时的总和,而是取决于耗时最长的那个长尾请求。

JavaScript中怎么并行发送多个HTTP请求并等待结果
图 1:串行顺序请求与并行并发请求的网络耗时与执行时序对比
核心优势:假如有三个分别耗时 300ms、400ms、200ms 的 HTTP 请求,串行等待需要耗费 900ms;而通过并行发起,三者同时在网络底层传输,总耗时仅由最慢的 400ms 决定,时间节省率超过 50%。

二、标准实现:使用 Promise.all 并行发起与数据解析

在浏览器原生环境或现代化 Node.js(v18+)中,fetch 是发起 HTTP 请求的标准 API。使用 Promise.all 并发处理多个请求时,需要特别注意 fetch 的“双阶段异步特性”:

  1. 网络响应阶段:fetch(url) 返回的 Promise 仅代表 HTTP 响应头已经到达(此时解析出 Response 对象)。
  2. 数据体读取阶段:调用 response.json() 或 response.text() 是另一个异步过程,需要再次等待流式数据读取完毕。

因此,最佳实践是在 map 映射回调中封装一个微型异步任务,将“发起网络请求”与“解析响应 JSON”打包为一个统一的 Promise,再将 Promise 数组交付给 Promise.all。

JavaScript中怎么并行发送多个HTTP请求并等待结果
图 2:并行发起 HTTP 请求并等待解析结果的标准操作全流程

完整代码示例:Promise.all 并行请求

// 1. 目标接口列表
const apiEndpoints = [
  'https://api.example.com/user/profile',
  'https://api.example.com/user/posts',
  'https://api.example.com/user/followers'
];

// 2. 并行请求并统一等待的执行函数
async function fetchDashboardData(urls) {
  try {
    // map 遍历时会立刻同步调用 fetch,所有请求同时发出,返回 Promise 数组
    const requestTasks = urls.map(async (url) => {
      const response = await fetch(url);
      
      // 注意:fetch 在遭遇 404 或 500 时不会自动 reject,必须手动校验 ok 状态
      if (!response.ok) {
        throw new Error(`请求失败: ${url} 状态码: ${response.status}`);
      }
      return await response.json();
    });

    // 3. 并行等待所有 Promise 完成解析
    const [profile, posts, followers] = await Promise.all(requestTasks);

    // 4. 消费已解构的业务数据
    console.log('个人资料:', profile);
    console.log('文章列表:', posts);
    console.log('粉丝列表:', followers);

    return { profile, posts, followers };
  } catch (error) {
    // 只要有任一接口报错,Promise.all 就会立即触发 catch
    console.error('聚合请求发生错误:', error.message);
    throw error;
  }
}
注意事项:Promise.all 返回的结果数组,其元素顺序与传入的任务数组顺序完全一致,绝不会因为哪个请求先返回而被打乱。因此开发者可以放心地通过数组解构(如 const [a, b, c] = results)按序取值。

三、容错与降级:Promise.all 与 Promise.allSettled 的选择

Promise.all 具备“全有或全无(Fail-Fast)”的特点。在实际生产业务中,并非所有接口都是强依赖的。如果页面的核心数据(如用户信息)已经成功获取,而附带的边缘数据(如推荐广告或天气微件)由于网络抖动发生 500 报错,使用 Promise.all 会导致整个页面直接崩溃进入错误分支。

为了解决局部降级问题,ECMAScript 2020 引入了 Promise.allSettled。它会始终等待全部请求履约完毕,无论每个请求是成功(fulfilled)还是失败(rejected),外部的 Promise 都会正常 resolve,并返回一个包含每个任务独立状态的对象数组。

JavaScript中怎么并行发送多个HTTP请求并等待结果
图 3:Promise.all 与 Promise.allSettled 的核心运行机制与决策矩阵

使用 Promise.allSettled 实现模块级容错

async function fetchSafeModules(tasks) {
  // 批量发出请求
  const promises = tasks.map(async (task) => {
    const res = await fetch(task.url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return { id: task.id, data: await res.json() };
  });

  // 使用 allSettled,永远不会直接抛出异常被 catch
  const settleResults = await Promise.allSettled(promises);

  // 遍历分流处理成功与失败项
  settleResults.forEach((item, index) => {
    if (item.status === 'fulfilled') {
      console.log(`模块 ${tasks[index].id} 加载成功:`, item.value.data);
      renderComponent(tasks[index].id, item.value.data);
    } else {
      console.warn(`模块 ${tasks[index].id} 降级展示:`, item.reason.message);
      renderFallbackPlaceholder(tasks[index].id);
    }
  });
}

四、进阶场景:限制最大并发数的“并发池”机制

虽然并行请求可以最大化降低耗时,但并行数量绝不是越多越好。在浏览器环境中,针对同一个域名的 HTTP/1.1 连接存在通常为 6 个的最大并发连接数限制;而在 HTTP/2 环境下,如果一次性发起上百个请求,也极易导致客户端 CPU 瞬时占用过高,或者触发服务端网关的限流规则(HTTP 429 Too Many Requests)。

当需要处理数十甚至上百个并发请求(如批量导出、大批量图片预加载、数据切片同步)时,标准的方案是引入并发池(Concurrency Pool),设定一个并发上限阈值(如每次最多运行 4 到 6 个请求),当有请求完成时立即从队列中填入新请求。

JavaScript中怎么并行发送多个HTTP请求并等待结果
图 4:大批量请求场景下的并发池队列调度机制

手写原生轻量级并发池(无需外部依赖)

/**
 * 原生并发池控制函数
 * @param {Array} poolList 待请求的数据项列表
 * @param {number} limit 最大并发限制数量
 * @param {Function} iteratorFn 处理单个项的异步函数
 */
async function asyncPool(poolList, limit, iteratorFn) {
  const allResults = [];
  const executingTasks = new Set();

  for (const item of poolList) {
    // 包装单个异步执行 Promise
    const task = Promise.resolve().then(() => iteratorFn(item));
    allResults.push(task);
    executingTasks.add(task);

    // 请求完成后将其从正在执行的 Set 集合中移出
    const cleanUp = () => executingTasks.delete(task);
    task.then(cleanUp, cleanUp);

    // 如果当前并发数达到了设定的上限,使用 Promise.race 等待其中任意一个槽位空出
    if (executingTasks.size >= limit) {
      await Promise.race(executingTasks);
    }
  }

  // 等待最后剩余在池中的所有任务完成并按原始顺序返回
  return Promise.all(allResults);
}

// 使用示例:批量下载 100 个资源,并发数严格限制为 5
const fileIds = Array.from({ length: 100 }, (_, i) => i + 1);

asyncPool(fileIds, 5, async (id) => {
  const res = await fetch(`/api/files/${id}`);
  return await res.json();
}).then(results => {
  console.log('全部 100 个文件获取完毕,数量:', results.length);
});

五、生产级避坑指南与最佳实践

在真实项目的并行请求开发中,除了选用正确的 Promise API 之外,还需要规避以下关键陷阱:

1. 必须配置 AbortController 防止长尾请求死锁

Promise.all 的整体执行时间完全受制于“最慢的那一个请求”。如果其中某个接口由于后端死锁或网络丢包陷入无限挂起,会导致整个聚合操作一直处于 Pending 状态。生产环境应为并行请求配置超时取消机制:

function fetchWithTimeout(url, timeoutMs = 8000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  return fetch(url, { signal: controller.signal })
    .then(res => {
      clearTimeout(timer);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    })
    .catch(err => {
      clearTimeout(timer);
      if (err.name === 'AbortError') {
        throw new Error(`请求超时: ${url}`);
      }
      throw err;
    });
}

2. 并行请求方案选型对照表

机制方案 失败响应行为 返回值结构 核心适用场景
Promise.all 立即 Reject 第一个错误(快速失败) 数据值数组 [A, B, C] 彼此强依赖的数据源、原子性操作、基础依赖加载
Promise.allSettled 等待全部结束,永远不直接 Reject 状态对象数组 [{status, value/reason}] 组件独立渲染、报表批量统计、多源异构数据采集与降级
并发池 (asyncPool) 结合 Promise.all/allSettled 进行错误处理 按原输入顺序返回的聚合结果 超过 10+ 的海量批量请求、网络爬虫、文件切片分块上传
总结:日常开发中,遇到数量有限(小于 10 个)且互不阻碍的 HTTP 请求时,优先通过 urls.map() 结合 Promise.all 发起;对于允许部分组件降级的看板页面,果断选用 Promise.allSettled;而在批量处理大量接口数据时,务必加上并发池限流,兼顾吞吐效率与系统稳定性。
作者最新文章
相关文章
更多