如何检测axios请求超时?IPFS最优网关选择方案求助
问题解决与优化方案
一、检测axios请求状态(超时/成功)
首先明确axios的请求逻辑:
- 成功响应:请求在
timeout时限内正常完成,会进入try块的后续代码,此时直接返回该网关URL即可。 - 超时失败:请求超过设定的
timeout,axios会抛出错误,且错误的code属性为ECONNABORTED,可在catch块中识别该类型。 - 其他错误:网关返回4xx/5xx状态码、网络连接失败等情况,同样会进入
catch块,需统一处理重试逻辑。
二、修正你的递归实现代码
原代码存在几个问题:没有返回Promise、可能重复尝试同一网关、无终止条件(极端情况会无限递归)。以下是修正后的版本:
const publicGateway = [ "https://ipfs.eth.aragon.network/ipfs/", "https://ipfs.io/ipfs/", "https://cloudflare-ipfs.com/ipfs/", "https://ipfs.fleek.co/ipfs/", "https://ipfs-infura.io/ipfs/", // ... more ] export const findPublicGateway = async (ipfs) => { // 复制网关列表,避免修改原数组 const availableGateways = [...publicGateway]; const getGateway = async () => { // 终止条件:无可用网关时抛出错误 if (availableGateways.length === 0) { throw new Error("所有IPFS网关均无法访问"); } // 随机选取并移除网关,避免重复尝试 const randomIndex = Math.floor(Math.random() * availableGateways.length); const gateway = availableGateways.splice(randomIndex, 1)[0]; const url = gateway + ipfs; try { // 仅请求文件头部字节,减少数据传输,加速判断 await axios({ method: 'get', url: url, timeout: 2000, headers: { 'Range': 'bytes=0-0' } }); // 请求成功,返回有效URL return url; } catch (error) { if (error.code === 'ECONNABORTED') { console.log(`网关 ${gateway} 超时,尝试下一个`); } else { console.log(`网关 ${gateway} 请求失败: ${error.message}`); } // 递归尝试下一个网关 return getGateway(); } } return getGateway(); }
改进点说明:
- 改为异步函数,返回Promise,调用方可通过
await获取结果。 - 每次尝试后移除已试过的网关,避免重复无效请求。
- 添加终止条件,防止无限递归。
- 使用
Range请求头仅获取文件第一个字节,大幅缩短响应判断时间。
三、更优实现方案
针对批量IPFS哈希渲染的场景,推荐以下几种更高效的方案:
1. 并行测试网关(最快响应优先)
逐个尝试网关效率低,可同时向多个网关发起请求,优先使用第一个成功响应的网关:
export const findPublicGatewayParallel = async (ipfs, maxParallel = 3) => { const availableGateways = [...publicGateway]; const promises = []; // 同时发起指定数量的并行请求 for (let i = 0; i < Math.min(maxParallel, availableGateways.length); i++) { const randomIndex = Math.floor(Math.random() * availableGateways.length); const gateway = availableGateways.splice(randomIndex, 1)[0]; const url = gateway + ipfs; const promise = axios({ method: 'get', url: url, timeout: 2000, headers: { 'Range': 'bytes=0-0' } }).then(() => url) .catch(() => null); // 失败返回null,不中断Promise.any promises.push(promise); } // 等待第一个成功的响应 try { const result = await Promise.any(promises); return result; } catch { // 第一批请求全失败,递归尝试剩余网关 if (availableGateways.length > 0) { return findPublicGatewayParallel(ipfs, maxParallel); } throw new Error("所有IPFS网关均无法访问"); } }
优势:利用并行请求,最快可在2秒内找到可用网关,比逐个尝试效率提升数倍。
2. 网关健康预检测+缓存
定期检测网关可用性,维护健康网关列表,避免每次都尝试失效网关:
// 缓存健康网关列表,定期更新 let healthyGateways = [...publicGateway]; // 每5分钟执行一次健康检测 setInterval(async () => { const healthChecks = publicGateway.map(gateway => axios({ method: 'head', url: gateway, timeout: 1000 }).then(() => gateway) .catch(() => null) ); const results = await Promise.all(healthChecks); healthyGateways = results.filter(g => g !== null); }, 5 * 60 * 1000); export const findPublicGatewayCached = async (ipfs) => { if (healthyGateways.length === 0) { throw new Error("无可用健康IPFS网关"); } const triedGateways = new Set(); while (triedGateways.size < healthyGateways.length) { const randomIndex = Math.floor(Math.random() * healthyGateways.length); const gateway = healthyGateways[randomIndex]; if (triedGateways.has(gateway)) continue; triedGateways.add(gateway); const url = gateway + ipfs; try { await axios({ method: 'get', url: url, timeout: 2000, headers: { 'Range': 'bytes=0-0' } }); return url; } catch (error) { console.log(`健康网关 ${gateway} 失效,尝试下一个`); } } throw new Error("所有健康网关均无法访问该IPFS哈希"); }
优势:提前过滤失效网关,减少无效尝试;缓存健康状态,降低重复检测开销。
3. 批量哈希复用网关
如果需要渲染一批IPFS哈希,可复用同一个可用网关,无需每个哈希重新查找:
export const renderBatchHashes = async (ipfsHashes) => { // 先找到一个可用网关 const gateway = await findPublicGatewayParallel(ipfsHashes[0]); // 用同一网关批量请求哈希 return Promise.all(ipfsHashes.map(hash => axios.get(gateway + hash) .then(res => ({ hash, data: res.data })) .catch(async () => { // 若该网关对当前哈希失效,单独为其查找新网关 const newGateway = await findPublicGatewayParallel(hash); return axios.get(newGateway + hash).then(res => ({ hash, data: res.data })); }) )); }
优势:减少网关查找次数,大幅提升批量处理效率。
内容的提问来源于stack exchange,提问作者SnowyField
相关产品推荐
相关产品推荐

