Promise Chain批量处理邮编请求数秒后变慢,求优化方案及学习资料
解决批量网络请求后期变慢的问题(Promise并发控制)
问题描述
我有一个包含2000个邮编的.txt文件,需要为每个邮编发起2-3次网络请求。测试时前200条处理速度很快,但之后脚本运行极慢。我知道当前实现是一次性并行发起所有请求,问题出在Promise的处理方式上,请问该怎么解决?有没有相关的学习资料?
原代码:
const axios = require('axios') const chalk = require('chalk') const fs = require('fs') const prompt = require('prompt-sync')() const getLocalization = async (zip) => { const config = { method: 'get', url: `viacep.com.br/ws/${zip}/json`, } try { const res = await axios(config) return res } catch (e) { console.error(e) } } const getCity = async (zip) => { // ... example } const main = async (zip) => { const disponibility = await getLocalization(zip) if (disponibility) { getCity(zip) } else { console.log('ZIP not Found') } } console.log(chalk.yellow('# Database Path:')) const filename = prompt('$ ') const parsered = fs.readFileSync(filename.includes('.txt') ? filename : `${filename}.txt`, 'utf8').split('\n') for (i = 0; i < parsered.length; i++) { const trimmedLine = parsered[i].trim().split('|')[0] main(trimmedLine) }
问题原因
当前代码在循环中直接调用main(trimmedLine)却不等待异步操作完成,导致瞬间创建2000个Promise并发起网络请求,触发两个核心问题:
- 本地资源耗尽:Node.js的HTTP连接池默认有数量限制,大量并发请求会导致后续请求排队等待可用连接,表现为处理速度骤降。
- 服务器限流:目标服务器(如viacep)会对高频请求进行限流,后续请求被延迟或拒绝,进一步拖慢处理速度。
解决方案
方案1:并发控制(分批处理)
这是最优解,限制同时发起的请求数量,比如每次处理10-20个邮编,完成一批后再处理下一批,兼顾效率与稳定性。
修改后的代码示例:
const axios = require('axios') const chalk = require('chalk') const fs = require('fs') const prompt = require('prompt-sync')() const getLocalization = async (zip) => { const config = { method: 'get', url: `https://viacep.com.br/ws/${zip}/json`, // 补充HTTPS协议头 timeout: 5000, // 添加超时,避免请求挂起占用资源 } try { const res = await axios(config) return res.data // 返回实际数据而非完整响应对象 } catch (e) { console.error(`获取邮编${zip}信息失败:`, e.message) return null } } const getCity = async (zip) => { // 示例逻辑,根据实际需求实现 try { console.log(`处理邮编${zip}的城市信息`) // 这里可以添加第二个网络请求逻辑 } catch (e) { console.error(`获取邮编${zip}城市信息失败:`, e.message) } } const main = async (zip) => { const disponibility = await getLocalization(zip) if (disponibility) { await getCity(zip) // 添加await,确保当前邮编的所有请求完成 } else { console.log(`邮编${zip}未找到`) } } // 分批处理函数:控制并发数量 const processInBatches = async (items, batchSize, handler) => { // 过滤空行,避免无效请求 const validItems = items.filter(line => line.trim()) for (let i = 0; i < validItems.length; i += batchSize) { const batch = validItems.slice(i, i + batchSize).map(line => line.trim().split('|')[0]) await Promise.all(batch.map(handler)) console.log(chalk.blue(`已完成第${Math.floor(i/batchSize)+1}批,共${Math.ceil(validItems.length/batchSize)}批`)) } } console.log(chalk.yellow('# Database Path:')) const filename = prompt('$ ') const parsered = fs.readFileSync(filename.includes('.txt') ? filename : `${filename}.txt`, 'utf8').split('\n') // 设置每批处理10个,可根据网络情况调整 processInBatches(parsered, 10, main)
方案2:串行处理(适合小批量测试)
如果不需要追求效率,只需要保证稳定,可以逐个处理每个邮编:
// 替换原循环部分 for (const line of parsered) { const trimmedLine = line.trim().split('|')[0] if (trimmedLine) await main(trimmedLine) }
额外优化点
- 给axios请求添加超时时间,避免因网络问题导致请求长时间挂起。
- 补充URL的协议头(
https://),否则axios无法正确发起请求。 - 在
main函数中给getCity添加await,确保单个邮编的所有请求都完成后再处理下一个。 - 过滤文件中的空行,减少无效请求。
相关学习知识点
- Promise与async/await异步流程
- 理解
Promise.all的作用:等待所有并行Promise完成,但必须控制并发数量。 - 避免在循环中直接调用异步函数却不等待,防止无限制并发。
- 理解
- Node.js网络请求限制
- 了解HTTP连接池的默认大小(默认5个),可通过
axios.defaults.httpAgent修改配置。 - 掌握TCP端口耗尽、连接排队的原理,理解大量并发请求拖慢系统的原因。
- 了解HTTP连接池的默认大小(默认5个),可通过
- 并发控制模式
- 分批处理(batch processing):将大数组拆分为小批量,逐批执行异步任务。
- 限流队列:使用队列动态控制同时执行的异步任务数量,适合复杂场景。
内容的提问来源于stack exchange,提问作者Dasha Delas
相关产品推荐
相关产品推荐

