node-fetch批量大数据并行调用API遇问题:分组请求全拒绝与错误捕获
问题描述
我有大量数据需要通过node-fetch发送至API,用Promise.allSettled实现并行发送,但数据量较大时不仅耗时极久,还会出现执行卡顿。
初始代码(小数据量正常,大数据量异常)
const fetch = require('node-fetch'); // ..... // 获取API令牌后调用发送数据的函数 await sendToApi(token); // ..... async function sendToApi(token) { // 已定义headers和requestOptions(使用token) try { const responses = await Promise.allSettled( data.map(async (Item) => { path = 'my/path/'; requestOptions.body = JSON.stringify(Item); const res = await fetch(path, requestOptions); console.log("res.status: ", res.status); return res; }) ); responses.forEach((response) => { console.log("response.status: ", response.status); }); } catch (error) { console.log("error: ", error); } }
尝试分组发送后的代码(所有Promise均rejected,try/catch无法捕获错误)
// 获取API令牌后调用发送数据的函数 await sendToApi(token); // ..... async function sendToApi(token) { // 已定义headers和requestOptions(使用token) // data为原始大数据数组(测试时已截断) await sendInGroups(data.slice(0, n), requestOptions); await sendInGroups(data.slice(n + 1, 2 * n), requestOptions); await sendInGroups(data.slice(2 * n + 1, 3 * n), requestOptions); // .... 以此类推 }
对应的sendInGroups函数:
async sendInGroups(data), requestOptions) { try { console.log("in try"); const responses = await Promise.allSettled( data.map(async (Item) => { path = 'my/path'; requestOptions.body = JSON.stringify(Item); const res = await fetch(path, requestOptions); console.log("res.status: ", res.status); return res; }) ); responses.forEach((response) => { console.log("response.status: ", response.status); }); } catch (error) { console.log(error); } } }
问题
- 基于现有代码结构,如何捕获被拒绝Promise的错误?
- 分组发送的逻辑是否错误?或是存在其他遗漏导致请求失败?
解决方案
1. 捕获被拒绝Promise的错误
Promise.allSettled本身不会抛出错误,它会返回每个Promise的状态结果,因此外层try/catch无法捕获内部Promise的失败。要获取错误信息,需遍历返回的responses数组,检查每个结果的状态:
responses.forEach((response, index) => { if (response.status === 'fulfilled') { console.log(`第${index+1}个请求成功,状态码:`, response.value.status); } else { console.error(`第${index+1}个请求失败,原因:`, response.reason); } });
另外注意你的sendInGroups函数存在语法错误:参数列表括号写错,正确写法是async function sendInGroups(data, requestOptions),这个语法错误是导致Promise全部rejected的直接原因之一。
2. 分组发送的问题排查与修正
核心问题点
slice用法错误:data.slice(n + 1, 2 * n)会跳过索引为n的元素,正确分组应为data.slice(i * n, (i + 1) * n),确保数据无遗漏。- 请求选项竞态问题:多个异步函数同时修改同一个
requestOptions对象的body属性,会导致所有请求最终使用最后一个Item的序列化结果,必须为每个请求创建独立的配置对象。 - 未处理HTTP非成功状态:即使
fetch返回响应,若状态码非2xx,也应视为失败并抛出错误,否则Promise.allSettled会将其标记为成功。
修正后的分组发送代码
async function sendToApi(token) { // 定义固定的基础请求头 const baseHeaders = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' // 其他固定headers... }; const batchSize = 10; // 根据API并发限制调整此值 // 自动循环分组,替代手动写多个await for (let i = 0; i < data.length; i += batchSize) { const batch = data.slice(i, i + batchSize); await sendInGroups(batch, baseHeaders); // 可选:每组发送后添加短暂延迟,避免触发API限流 await new Promise(resolve => setTimeout(resolve, 500)); } } async function sendInGroups(data, baseHeaders) { try { const responses = await Promise.allSettled( data.map(async (item) => { const path = 'my/path/'; // 为每个请求创建独立的配置对象 const requestOptions = { method: 'POST', // 根据实际请求方法调整 headers: {...baseHeaders}, // 复制基础headers,避免引用共享 body: JSON.stringify(item) }; const res = await fetch(path, requestOptions); // 处理HTTP非成功状态 if (!res.ok) { throw new Error(`请求失败:${res.status},响应内容:${await res.text()}`); } return res; }) ); // 遍历输出所有请求结果 responses.forEach((response, index) => { if (response.status === 'fulfilled') { console.log(`批次内第${index+1}个请求成功,状态码:`, response.value.status); } else { console.error(`批次内第${index+1}个请求失败:`, response.reason); } }); } catch (error) { console.error("分组发送时出现全局错误:", error); } }
关键优化说明
- 每个请求使用独立的
requestOptions,彻底解决竞态问题。 - 自动循环分组,代码更易维护,避免手动切片的遗漏。
- 显式处理HTTP非2xx状态码,让失败请求能被
Promise.allSettled正确捕获。 - 可选的组间延迟,降低触发API限流的风险。
内容的提问来源于stack exchange,提问作者m-alptekin
相关产品推荐
相关产品推荐

