You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,确保单个邮编的所有请求都完成后再处理下一个。
  • 过滤文件中的空行,减少无效请求。

相关学习知识点

  1. Promise与async/await异步流程
    • 理解Promise.all的作用:等待所有并行Promise完成,但必须控制并发数量。
    • 避免在循环中直接调用异步函数却不等待,防止无限制并发。
  2. Node.js网络请求限制
    • 了解HTTP连接池的默认大小(默认5个),可通过axios.defaults.httpAgent修改配置。
    • 掌握TCP端口耗尽、连接排队的原理,理解大量并发请求拖慢系统的原因。
  3. 并发控制模式
    • 分批处理(batch processing):将大数组拆分为小批量,逐批执行异步任务。
    • 限流队列:使用队列动态控制同时执行的异步任务数量,适合复杂场景。

内容的提问来源于stack exchange,提问作者Dasha Delas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 05:15:35