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

Node.js fetch循环处理26个URL过慢 Vercel部署触发504超时如何解决

问题根因分析
  1. 初始代码性能问题:你在循环中使用await逐个发起请求,所有请求为串行执行,26个请求的总耗时等于所有单个请求耗时的叠加,只要单个请求平均耗时超过200ms,总耗时就会超过5秒,属于逻辑实现问题,不是Node.js fetch的固有特性。
  2. 并发后仍超时的原因:Vercel免费版Serverless函数冷启动本身有数百ms延迟,加上公网请求第三方站点的网络波动,只要有2-3个站点的响应超过2秒,整体并发耗时就会触达5秒的免费版上限。
可行解决方案
  • 方案1:并发请求+单请求超时控制+失败降级
    使用Promise.allSettled批量处理并发请求,给每个fetch设置最大超时时间(如800ms),超时的站点直接跳过,不阻塞整体返回。
  • 方案2:调整Vercel函数超时限制
    若你是Vercel付费用户,可在项目根目录的vercel.json中配置函数最大执行时长,最高可设为30秒,配置示例:
    {
      "functions": {
        "api/*.js": {
          "maxDuration": 10
        }
      }
    }
    
  • 方案3:前端拆分批量请求
    免费版用户可让前端将26个站点拆分为3-4组,分组调用接口,每次接口仅处理一组站点的请求,前端收到所有分组结果后自行合并,单次接口耗时可稳定控制在2秒以内。
  • 方案4:增加缓存策略
    如果目标站点的/items.json数据更新频率较低,可以用Vercel KV或者Edge Cache缓存已经拉取过的站点数据,缓存有效期内的请求直接返回缓存结果,耗时可降低到100ms以内。
优化后代码示例(带超时的并发实现)
// 封装带超时的fetch
const fetchWithTimeout = (url, timeout = 800) => {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Request timeout')), timeout)
    )
  ])
}

export default async (req, res) => {
  if (req.method !== 'POST') {
    return res.status(405).end()
  }
  try {
    const body = JSON.parse(req.body)
    const sites = body.list || []
    const siteLists = []

    // 生成所有请求的Promise数组
    const requestPromises = sites.map(async (site, index) => {
      if (!site?.url) return null
      try {
        const jurl = `${site.url}/items.json`
        const fUrl = await fetchWithTimeout(jurl)
        const siteData = await fUrl.json()
        if (!siteData?.items?.length) return null
        const items = siteData.items.map(item => ({
          url: site.url,
          img: item.images[0].url,
          title: item.title
        }))
        return {
          id: index,
          name: site.name || "",
          label: site.label || "",
          items: items
        }
      } catch (err) {
        return null
      }
    })

    // 批量处理所有请求结果
    const results = await Promise.allSettled(requestPromises)
    results.forEach(result => {
      if (result.status === 'fulfilled' && result.value) {
        siteLists.push(result.value)
      }
    })

    return res.send({ sites: siteLists })
  } catch (err) {
    return res.status(500).send({ error: 'Internal error' })
  }
}

内容的提问来源于stack exchange,提问作者Nathan Bernard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 16:15:07