如何限制.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
相关产品推荐
相关产品推荐

