使用Axios批量发起大量HTTP请求的最佳实践方案
问题根因
你当前代码的两个问题本质上是异步逻辑写法错误导致的:
forEach本身不支持异步等待,传入的async回调会被直接加入异步任务队列,forEach执行完同步逻辑后就会直接返回,既不会等待前一个请求完成再触发下一个,也不会把回调里的异步错误抛到外层,所以请求顺序完全随机,部分错误也捕获不到。searchContent里catch到错误后直接return错误对象,相当于主动吞掉了异常,外层拿到返回值时无法区分是正常响应内容还是错误对象;另外axios响应对象的data属性是同步字段,不需要额外加await,属于冗余写法。
实现方案
根据你对请求顺序、性能的要求选对应写法即可。
方案1:严格按列表顺序串行请求(100%保证执行顺序)
适合对请求先后顺序有强要求的场景,前一个请求拿到结果(成功/失败)后才会发起下一个请求,所有错误都能被精准捕获:
async function searchContent(url) { const response = await axios(url); return response.data; } async function start() { const allResults = []; // 用for...of循环才能正确等待每一步异步执行 for (const site of sites) { const targetUrl = `https://${site.URL}`; try { const content = await searchContent(targetUrl); allResults.push({ url: targetUrl, success: true, data: content }); console.log(`[成功] ${targetUrl}`, content); } catch (err) { allResults.push({ url: targetUrl, success: false, error: err }); console.log(`[失败] ${targetUrl}`, err); } } // 所有请求全部处理完成后才会执行到这里 console.log('全部请求处理完毕', allResults); return allResults; }
方案2:并发批量请求(性能更高,结果顺序与列表一致)
如果站点数量多、不需要严格的请求先后顺序,用并发请求速度更快,配合Promise.allSettled可以拿到所有请求的结果,不会因为单个请求失败导致整个流程中断,最终返回的结果数组顺序和原站点列表顺序完全一致:
async function start() { // 批量生成请求任务 const requestTasks = sites.map(site => { const targetUrl = `https://${site.URL}`; return searchContent(targetUrl) .then(data => ({ url: targetUrl, success: true, data: data })) .catch(err => ({ url: targetUrl, success: false, error: err })); }); // 等待所有请求(无论成功失败)全部完成 const allResults = await Promise.allSettled(requestTasks); // 统一处理结果 allResults.forEach(taskRes => { if (taskRes.status === 'fulfilled') { console.log(taskRes.value); } }); console.log('全部请求处理完毕', allResults); return allResults; }
如果站点数量过大,一次性发全部请求可能触发浏览器同域并发限制、打挂目标站点,可以加一层并发数控制,比如最多同时发3个请求:
// 通用并发池工具 async function asyncPool(limit, list, iterator) { const results = []; const runningTasks = []; for (const item of list) { const task = Promise.resolve().then(() => iterator(item)); results.push(task); if (limit <= list.length) { const done = task.then(() => runningTasks.splice(runningTasks.indexOf(done), 1)); runningTasks.push(done); if (runningTasks.length >= limit) { await Promise.race(runningTasks); } } } return Promise.all(results); } // 调用示例,最大并发数设为3 async function start() { const allResults = await asyncPool(3, sites, async (site) => { const targetUrl = `https://${site.URL}`; try { const content = await searchContent(targetUrl); return { url: targetUrl, success: true, data: content }; } catch (err) { return { url: targetUrl, success: false, error: err }; } }); console.log('全部请求处理完毕', allResults); return allResults; }
避坑提醒
- 不要用forEach、map这类数组遍历方法处理需要串行等待的异步逻辑,这类方法内部没有对异步回调的等待机制,只会同步注册所有回调,顺序和错误都不可控。
- 批量异步任务不要直接用
Promise.all,只要其中一个任务reject,整个Promise.all会立即失败,其他还在执行的任务结果会丢失,需要拿全量结果时优先用Promise.allSettled。 - 异步请求不要在底层通用方法里吞错,要么返回带明确状态标识的结构,要么把错误向上抛出交给业务层处理,避免出现错误被静默吃掉难以排查的问题。
内容的提问来源于stack exchange,提问作者Boruto
相关产品推荐
相关产品推荐

