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

JS脚本多次API调用导致运行耗时过长 优化方案咨询

性能问题诊断

当前耗时过长不是业务场景的必然结果,核心问题是代码的请求逻辑全为串行执行,网络IO等待时间被成倍拉长:

  • 前两个分页拉取接口无参数依赖,现有逻辑大概率是等第一个接口全部分页拉取完成后,才启动第二个接口的拉取,总耗时为两个接口耗时之和
  • 第三个详情接口在for循环中逐个await请求,始终保持1个并发,假设单个请求响应耗时200ms,1000个ID就需要200秒等待时间,这部分占了总耗时的90%以上
  • 现有代码存在基础逻辑错误:URL中的参数(日期、分页令牌、ID)均为硬编码字符串,没有做变量插值,实际运行无法正确传递参数
具体优化方案

1. 无依赖接口并行执行

前两个分页接口的拉取流程完全独立,不需要互相等待结果,可以并行启动,总耗时等于两个接口中较慢者的耗时,而非两者相加。
可以封装通用分页拉取逻辑避免重复代码:

// 通用全量分页拉取工具
async function fetchAllPages(fetchApi, accessToken, start, end) {
  const allRecords = []
  let nextPageToken = ''
  do {
    const res = await fetchApi(accessToken, start, end, nextPageToken)
    // 按实际接口返回的列表字段调整取值
    allRecords.push(...res.data_list)
    nextPageToken = res.next_page_token
  } while (nextPageToken)
  return allRecords
}

// 并行拉取两个接口的全量分页数据
const [meetingList, chatList] = await Promise.all([
  fetchAllPages(getFirstAPI, access_token, start, end),
  fetchAllPages(getSecondAPI, access_token, start, end)
])
// 提取所有需要查询详情的ID
const allTargetIds = [
  ...meetingList.map(item => item.id),
  ...chatList.map(item => item.id)
]

注意分页本身的串行逻辑需要保留,因为下一页的请求必须依赖上一页返回的next_page_token,这部分无法并行。

2. 详情接口添加并发控制,替代串行循环

1并发的串行请求是性能最大瓶颈,但不要无脑发起全量并发,否则很容易触发接口流控(返回429错误),建议根据对接API的流控阈值设置3-10的固定并发数,性能可以直接提升数倍到数十倍。
无需引入第三方依赖的并发控制实现如下:

/**
 * 并发控制请求工具
 * @param {Array} items 待请求的参数列表
 * @param {number} concurrency 最大并发数
 * @param {Function} requestFn 单条请求逻辑
 * @returns {Array} 请求结果,顺序和入参数组一致
 */
async function concurrentRequest(items, concurrency, requestFn) {
  const results = []
  let cursor = 0
  // 启动指定数量的工作协程
  async function worker() {
    while (cursor < items.length) {
      const currentIdx = cursor++
      const currentParam = items[currentIdx]
      // 内置错误重试,碰到限流/网络错误最多重试2次
      let retryCount = 0
      while (retryCount <= 2) {
        try {
          results[currentIdx] = await requestFn(currentParam)
          break
        } catch (err) {
          retryCount++
          if (retryCount > 2) throw err
          // 指数退避等待,避免持续触发限流
          await new Promise(resolve => setTimeout(resolve, 1000 * 2 ** retryCount))
        }
      }
    }
  }
  await Promise.all(Array.from({ length: concurrency }, () => worker()))
  return results
}

调用时替换原有串行循环:

// 示例设置5个并发,可根据API流控规则调整
const detailList = await concurrentRequest(allTargetIds, 5, async (id) => {
  // 注意修复原代码的硬编码问题,用模板字符串正确拼接URL参数
  const url = `baseAPI/participants/${id}`
  const resp = await fetch(url, {
    method: 'GET',
    headers: { authorization: `Bearer ${access_token}` }
  })
  if (resp.status === 429) throw new Error('rate limit exceeded')
  return resp.json()
})
globalObject.store_data.push(...detailList)

3. 附加优化点

  • 优先使用API提供的批量查询接口:如果对接的接口支持一次传入多个ID批量查询详情,直接用批量接口替代单条查询,性能提升会更明显
  • 拉取和入库并行:不要等所有数据全部拉完再写入数据库,每拉取到50-100条数据就批量执行一次入库操作,拉取后续数据和入库可以同时进行,进一步压缩总耗时
  • 做增量拉取逻辑:已经拉取并存入数据库的数据,后续不需要重复请求,只拉取新增时间范围的数据即可,避免重复请求浪费时间
  • 修复原代码的硬编码问题:所有URL中的日期、分页令牌、ID参数都需要通过模板字符串正确插值,否则接口无法返回正确结果
效果预估

按当前单日14分20秒的耗时计算,90%以上的耗时来自串行的详情请求,开启5并发即可将这部分耗时压缩到原来的1/5左右,加上前两个接口并行拉取的优化,单日数据拉取总耗时可以稳定控制在2分钟以内,扩大日期范围也不会出现耗时数小时的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:27:18