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

如何限制.map()执行速率 避免并发fetch()请求触发503错误

问题根因

原有代码中Array.map()搭配Promise.all()的写法会瞬间并发发起所有fetch请求,你调用的Trezor公开区块浏览器接口有明确的速率限制,请求间隔小于1s、并发数超过1就会触发限流,返回503服务不可用响应,这就是单钱包正常、多钱包并发直接报错的核心原因。另外原有代码没有校验HTTP响应状态,503返回的是HTML错误页,直接调用response.json()必然触发解析失败。

实现方案

方案1:最简单的串行+固定间隔请求(适合钱包数量少的场景)

直接按顺序逐个请求,每个请求之间留1s间隔,完全匹配你测试下来接口能稳定响应的节奏,代码改动最小:

// 固定延迟工具函数
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

static async getWalletBalances(_accountsList) {
  try {
    const result = []
    for (const account of _accountsList) {
      const processedWallets = []
      for (const wallet of account.wallets) {
        // 先校验响应状态,非200直接抛错
        const response = await fetch(`https://btc1.trezor.io/api/v2/xpub/${wallet.walletXpub}`)
        if (!response.ok) throw new Error(`请求失败,状态码:${response.status}`)
        const data = await response.json()
        processedWallets.push({ ...wallet, balance: data.balance })
        // 每个钱包请求完成后等待1s再发起下一个
        await sleep(1000)
      }
      result.push({ ...account, wallets: processedWallets })
    }
    return result
  } catch (error) {
    console.log(`获取余额失败: ${error}`)
  }
}

方案2:带并发控制+自动重试(生产级通用方案)

如果后续钱包数量多,全串行速度太慢,可以实现轻量限流池,控制同时最多只有1个请求在飞(适配该接口的限流规则),遇到503自动等待后重试,不需要手动写死所有等待时间:

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

// 异步限流池:控制同时执行的异步任务数量
async function asyncPool(concurrency, tasks, iteratorFn) {
  const results = []
  const executing = new Set()
  for (const item of tasks) {
    const p = Promise.resolve().then(() => iteratorFn(item))
    results.push(p)
    executing.add(p)
    const clean = () => executing.delete(p)
    p.then(clean).catch(clean)
    if (executing.size >= concurrency) {
      await Promise.race(executing)
    }
  }
  return Promise.all(results)
}

// 带重试的请求封装,遇到503自动重试
async function fetchBalance(xpub, retryTimes = 2) {
  try {
    const response = await fetch(`https://btc1.trezor.io/api/v2/xpub/${xpub}`)
    // 触发限流就等2s重试
    if (response.status === 503 && retryTimes > 0) {
      await sleep(2000)
      return fetchBalance(xpub, retryTimes - 1)
    }
    if (!response.ok) throw new Error(`请求状态异常:${response.status}`)
    const data = await response.json()
    return data.balance
  } catch (err) {
    if (retryTimes > 0) {
      await sleep(1000)
      return fetchBalance(xpub, retryTimes - 1)
    }
    throw err
  }
}

static async getWalletBalances(_accountsList) {
  try {
    // 拍平所有钱包,统一做限流处理
    const allWallets = []
    const accountMap = new Map()
    for (const account of _accountsList) {
      // 用你自己的account唯一标识替换id字段
      accountMap.set(account.id, { ...account, wallets: [] })
      for (const wallet of account.wallets) {
        allWallets.push({ wallet, accountId: account.id })
      }
    }

    // 并发数设为1,匹配接口限流规则
    await asyncPool(1, allWallets, async ({ wallet, accountId }) => {
      const balance = await fetchBalance(wallet.walletXpub)
      accountMap.get(accountId).wallets.push({ ...wallet, balance })
    })

    return Array.from(accountMap.values())
  } catch (error) {
    console.log(`获取余额失败: ${error}`)
  }
}
注意事项
  • 不要直接用map + Promise.all处理有速率限制的接口请求:该写法无任何并发控制,列表有多少项就会瞬间发出多少个请求,极容易触发第三方接口限流
  • 所有fetch请求必须先校验response.ok状态再做格式解析:HTTP错误状态(比如503、429)返回的通常是HTML错误页,直接调用response.json()会触发额外的格式解析错误
  • 如果后续钱包数量较多、调用频率高,建议搭建自有服务端做余额查询和缓存,不要直接在前端调用第三方公开接口,稳定性无法保障

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:06:30