请求超时重试封装

news/2025/10/14 12:27:18/文章来源:https://www.cnblogs.com/amulong1237/p/19140409

请求超时重试封装

1. 基础版本 - 带指数退避的重试机制

interface RetryConfig {maxRetries?: number;          // 最大重试次数baseDelay?: number;           // 基础延迟时间(ms)timeout?: number;             // 单次请求超时时间(ms)retryCondition?: (error: any) => boolean; // 重试条件
}class RetryableRequest {private config: Required<RetryConfig>;constructor(config: RetryConfig = {}) {this.config = {maxRetries: config.maxRetries ?? 3,baseDelay: config.baseDelay ?? 1000,timeout: config.timeout ?? 10000,retryCondition: config.retryCondition ?? this.defaultRetryCondition};}private defaultRetryCondition(error: any): boolean {// 网络错误、超时、5xx 状态码时重试return !error.response || error.code === 'ECONNABORTED' || error.response.status >= 500;}private sleep(ms: number): Promise<void> {return new Promise(resolve => setTimeout(resolve, ms));}private calculateDelay(retryCount: number): number {// 指数退避算法:baseDelay * 2^retryCount + 随机抖动const exponentialDelay = this.config.baseDelay * Math.pow(2, retryCount);const jitter = Math.random() * 1000; // 0-1000ms 随机抖动return exponentialDelay + jitter;}async request<T>(requestFn: () => Promise<T>,customConfig?: Partial<RetryConfig>): Promise<T> {const config = { ...this.config, ...customConfig };let lastError: any;for (let retryCount = 0; retryCount <= config.maxRetries; retryCount++) {try {// 创建超时 Promiseconst timeoutPromise = new Promise<never>((_, reject) => {setTimeout(() => reject(new Error('Request timeout')), config.timeout);});// 竞速:请求 vs 超时const result = await Promise.race([requestFn(), timeoutPromise]);return result;} catch (error: any) {lastError = error;// 检查是否应该重试const shouldRetry = retryCount < config.maxRetries && config.retryCondition(error);if (shouldRetry) {const delay = this.calculateDelay(retryCount);console.warn(`请求失败,${delay}ms后重试 (${retryCount + 1}/${config.maxRetries}):`, error.message);await this.sleep(delay);continue;}break;}}throw lastError;}
}

2. 增强版本 - 支持不同策略和事件监听

enum RetryStrategy {EXPONENTIAL_BACKOFF = 'exponential',  // 指数退避FIXED = 'fixed',                      // 固定间隔LINEAR = 'linear'                     // 线性增长
}interface EnhancedRetryConfig {maxRetries?: number;baseDelay?: number;timeout?: number;strategy?: RetryStrategy;retryCondition?: (error: any) => boolean;onRetry?: (retryCount: number, error: any, delay: number) => void;onSuccess?: (result: any, retryCount: number) => void;onFailure?: (error: any, retryCount: number) => void;
}class EnhancedRetryableRequest {private config: Required<EnhancedRetryConfig>;constructor(config: EnhancedRetryConfig = {}) {this.config = {maxRetries: config.maxRetries ?? 3,baseDelay: config.baseDelay ?? 1000,timeout: config.timeout ?? 10000,strategy: config.strategy ?? RetryStrategy.EXPONENTIAL_BACKOFF,retryCondition: config.retryCondition ?? this.defaultRetryCondition,onRetry: config.onRetry ?? (() => {}),onSuccess: config.onSuccess ?? (() => {}),onFailure: config.onFailure ?? (() => {})};}private defaultRetryCondition(error: any): boolean {// 可重试的错误类型const retryableErrors = ['ECONNABORTED', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNRESET', 'ECONNREFUSED'];if (error.code && retryableErrors.includes(error.code)) {return true;}if (error.response) {// 5xx 服务器错误或 429 太多请求return error.response.status >= 500 || error.response.status === 429;}// 网络错误、超时等return error.message?.includes('timeout') || error.message?.includes('network');}private calculateDelay(retryCount: number): number {const baseDelay = this.config.baseDelay;switch (this.config.strategy) {case RetryStrategy.FIXED:return baseDelay;case RetryStrategy.LINEAR:return baseDelay * (retryCount + 1);case RetryStrategy.EXPONENTIAL_BACKOFF:default:const exponentialDelay = baseDelay * Math.pow(2, retryCount);const jitter = Math.random() * baseDelay * 0.1; // 10% 随机抖动return exponentialDelay + jitter;}}private sleep(ms: number): Promise<void> {return new Promise(resolve => setTimeout(resolve, ms));}async request<T>(requestFn: () => Promise<T>,customConfig?: Partial<EnhancedRetryConfig>): Promise<T> {const config = { ...this.config, ...customConfig };let lastError: any;let retryCount = 0;for (; retryCount <= config.maxRetries; retryCount++) {try {// 创建超时控制器const controller = new AbortController();const timeoutId = setTimeout(() => controller.abort(), config.timeout);let result: T;// 如果请求函数支持 signal,传递 abort signalif (typeof requestFn === 'function') {const requestResult = requestFn();// 检查是否是 fetch 风格的请求if (requestResult && typeof (requestResult as any).catch === 'function') {// 普通 Promiseresult = await Promise.race([requestResult,new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Request timeout')), config.timeout))]);} else {result = await requestResult;}} else {throw new Error('Invalid request function');}clearTimeout(timeoutId);// 请求成功if (retryCount > 0) {config.onSuccess(result, retryCount);}return result;} catch (error: any) {clearTimeout(timeoutId);lastError = error;// 检查是否应该重试const shouldRetry = retryCount < config.maxRetries && config.retryCondition(error);if (shouldRetry) {const delay = this.calculateDelay(retryCount);// 触发重试事件config.onRetry(retryCount + 1, error, delay);console.warn(`请求失败,${delay}ms后第${retryCount + 1}次重试:`, error.message);await this.sleep(delay);continue;}break;}}// 所有重试都失败了config.onFailure(lastError, retryCount);throw lastError;}// 便捷方法:创建不同策略的实例static createExponential(config: Omit<EnhancedRetryConfig, 'strategy'> = {}) {return new EnhancedRetryableRequest({ ...config, strategy: RetryStrategy.EXPONENTIAL_BACKOFF });}static createFixed(config: Omit<EnhancedRetryConfig, 'strategy'> = {}) {return new EnhancedRetryableRequest({ ...config, strategy: RetryStrategy.FIXED });}static createLinear(config: Omit<EnhancedRetryConfig, 'strategy'> = {}) {return new EnhancedRetryableRequest({ ...config, strategy: RetryStrategy.LINEAR });}
}

3. Axios 集成版本

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';class AxiosRetryAdapter {private axiosInstance: AxiosInstance;private retryableRequest: EnhancedRetryableRequest;constructor(axiosInstance?: AxiosInstance, retryConfig?: EnhancedRetryConfig) {this.axiosInstance = axiosInstance || axios.create();this.retryableRequest = new EnhancedRetryableRequest(retryConfig);this.setupInterceptors();}private setupInterceptors() {// 请求拦截器 - 添加重试逻辑this.axiosInstance.interceptors.request.use((config) => {// 如果配置了重试,使用重试逻辑if (config.retryConfig) {const originalRequest = () => this.axiosInstance.request(config);return this.retryableRequest.request(originalRequest, config.retryConfig).then(response => {// 重试逻辑已经处理,直接返回结果throw new axios.Cancel('Request handled by retry logic');}).catch(error => {if (axios.isCancel(error) && error.message === 'Request handled by retry logic') {// 这是预期的取消,实际结果在重试逻辑中处理return Promise.reject(error);}throw error;});}return config;},(error) => Promise.reject(error));}// 扩展 AxiosRequestConfig 类型request<T = any>(config: AxiosRequestConfig & { retryConfig?: Partial<EnhancedRetryConfig> }): Promise<AxiosResponse<T>> {if (config.retryConfig) {const requestFn = () => this.axiosInstance.request(config);return this.retryableRequest.request(requestFn, config.retryConfig);}return this.axiosInstance.request(config);}get<T = any>(url: string, config?: AxiosRequestConfig & { retryConfig?: Partial<EnhancedRetryConfig> }): Promise<AxiosResponse<T>> {return this.request({ ...config, method: 'GET', url });}post<T = any>(url: string, data?: any, config?: AxiosRequestConfig & { retryConfig?: Partial<EnhancedRetryConfig> }): Promise<AxiosResponse<T>> {return this.request({ ...config, method: 'POST', url, data });}// 其他 HTTP 方法...
}

4. 使用示例

// 1. 基础使用
const retryRequest = new EnhancedRetryableRequest({maxRetries: 3,baseDelay: 1000,timeout: 5000,onRetry: (retryCount, error, delay) => {console.log(`第${retryCount}次重试,原因: ${error.message}`);}
});// 2. 使用 fetch
async function fetchWithRetry() {try {const response = await retryRequest.request(() => fetch('https://api.example.com/data').then(res => {if (!res.ok) throw new Error(`HTTP ${res.status}`);return res.json();}));console.log('获取数据成功:', response);} catch (error) {console.error('所有重试都失败了:', error);}
}// 3. 使用 Axios
const axiosRetry = new AxiosRetryAdapter(axios, {maxRetries: 3,strategy: RetryStrategy.EXPONENTIAL_BACKOFF
});async function axiosWithRetry() {try {const response = await axiosRetry.get('https://api.example.com/data', {retryConfig: {maxRetries: 2,retryCondition: (error) => {// 只在网络错误和 500 状态码时重试return !error.response || error.response.status >= 500;}}});console.log('请求成功:', response.data);} catch (error) {console.error('请求失败:', error);}
}// 4. 不同策略的实例
const fixedRetry = EnhancedRetryableRequest.createFixed({maxRetries: 5,baseDelay: 2000
});const linearRetry = EnhancedRetryableRequest.createLinear({maxRetries: 3,baseDelay: 1000
});// 5. 模拟测试
async function testRetry() {let attempt = 0;const result = await retryRequest.request(async () => {attempt++;console.log(`第${attempt}次尝试`);if (attempt < 3) {throw new Error('模拟失败');}return { data: '成功数据' };});console.log('最终结果:', result);
}

5. React Hook 版本

import { useState, useCallback } from 'react';export function useRetryableRequest(config?: EnhancedRetryConfig) {const [loading, setLoading] = useState(false);const [error, setError] = useState<any>(null);const [data, setData] = useState<any>(null);const [retryCount, setRetryCount] = useState(0);const retryableRequest = new EnhancedRetryableRequest({...config,onRetry: (count, error, delay) => {setRetryCount(count);config?.onRetry?.(count, error, delay);},onSuccess: (result, count) => {setRetryCount(0);config?.onSuccess?.(result, count);},onFailure: (error, count) => {setRetryCount(count);config?.onFailure?.(error, count);}});const execute = useCallback(async <T>(requestFn: () => Promise<T>) => {setLoading(true);setError(null);try {const result = await retryableRequest.request(requestFn);setData(result);return result;} catch (err) {setError(err);throw err;} finally {setLoading(false);}}, [retryableRequest]);return {execute,loading,error,data,retryCount,reset: () => {setLoading(false);setError(null);setData(null);setRetryCount(0);}};
}// React 使用示例
function MyComponent() {const { execute, loading, error, data, retryCount } = useRetryableRequest({maxRetries: 3});const fetchData = async () => {try {await execute(() => fetch('/api/data').then(res => res.json()));} catch (err) {// 错误处理}};return (<div><button onClick={fetchData} disabled={loading}>{loading ? `加载中... (重试 ${retryCount})` : '获取数据'}</button>{error && <div>错误: {error.message}</div>}{data && <div>数据: {JSON.stringify(data)}</div>}</div>);
}

主要特性

  1. 多种重试策略: 指数退避、固定间隔、线性增长
  2. 智能重试条件: 网络错误、超时、5xx状态码自动重试
  3. 超时控制: 单次请求超时和总超时控制
  4. 事件监听: 重试、成功、失败事件回调
  5. 随机抖动: 避免惊群效应
  6. TypeScript 支持: 完整的类型定义
  7. 框架集成: 支持 Axios、Fetch、React Hook

这个封装提供了灵活的重试机制,可以根据具体需求调整重试策略和参数,有效处理网络不稳定的情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/936766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完整教程:数据结构 01 线性表

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

编程脉络梳理

编程脉络梳理编程脉络梳理 Java基础 源码和原理ThreadLocal 内存溢出问题 和 java引用类型定时任务Timer的原理和使用hashMap扩容和转红黑树条件Serializable接口 和 serialVersionUID 的关系指针压缩原理和为什么指针…

Emacs常用的一些快捷键,记不住的,方便查询!!

emacs 快捷键 基本快捷键(Basic) C-x C-f "find"文件, 即在缓冲区打开/新建一个文件 C-x C-s 保存文件 C-x C-w 使用其他文件名另存为文件 C-x C-v 关闭当前缓冲区文件并打开新文件 C-x i 在当前光标处插入文…

Microsoft Visual C++,Microsoft Visual Studio for Office Runtime,Microsoft Visual Basic Runtime等下载

Visual C++ 运行库合集(VCRedistPack),“缺少运行库”报错等问题修复 这个没什么好说的,就是解决常见的Visual C++ 运行库问题,一搬安装软件,比如PS,CAD等,也有因为安装游戏时出现的一些未知错误,“缺少运行库…

2025 年耐热钢厂家及热处理工装设备厂家推荐榜:多用炉/真空炉/台车炉/井式炉/箱式炉/耐热钢工装厂家,聚焦高效适配,助力企业精准选型

随着工业制造向高端化、精密化升级,热处理、冶金、石化等行业对耐热钢材料及专用工装设备的性能要求持续提升,兼具耐高温、耐腐蚀、高强度特性的耐热钢产品,已成为保障生产稳定性、提升工艺水平的核心要素。2025 年…

实用指南:如何进行WGBS的数据挖掘——从甲基化水平到功能通路

实用指南:如何进行WGBS的数据挖掘——从甲基化水平到功能通路pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Con…

python对接印度尼西亚股票数据接口文档

概述 StockTV 提供全面的印度尼西亚股票市场数据接口,覆盖印尼证券交易所(IDX)所有上市公司。支持实时行情、技术分析、公司信息等多种功能。 交易所信息交易所: 印尼证券交易所 (Indonesia Stock Exchange, IDX) 国…

实用指南:Python学习历程——基础语法(print打印、变量、运算)

实用指南:Python学习历程——基础语法(print打印、变量、运算)2025-10-14 11:59 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !im…

2025年舒适轮胎厂家最新权威推荐榜:静音耐磨,驾驶体验全面升级!

2025年舒适轮胎厂家最新权威推荐榜:静音耐磨,驾驶体验全面升级!随着汽车行业的快速发展,消费者对驾驶体验的要求越来越高。舒适轮胎作为提升驾驶体验的关键因素之一,其市场需求也日益增长。为了帮助筛选舒适轮胎品…

2025年耐磨轮胎厂家最新推荐排行榜,矿山耐磨轮胎,工程耐磨轮胎,重载耐磨轮胎公司推荐!

2025年耐磨轮胎厂家最新推荐排行榜,矿山耐磨轮胎,工程耐磨轮胎,重载耐磨轮胎公司推荐!随着工业和矿业的快速发展,对耐磨轮胎的需求日益增长。耐磨轮胎在矿山、工程和重载运输等领域的应用越来越广泛,其性能直接影…

Map做数据缓存

Map 的好处:键可以是任意类型(包括对象)保持插入顺序查找性能优于普通对象(尤其是大量键时)// 创建缓存 const cache = new Map();// 存入数据 cache.set(user_1, { name: Alice, age: 25 });// 读取数据 if (cac…

Python基于 Gradio 和 SQLite 开发的简单博客管理平台,承受局域网手机查看,给一个PC和手机 互联方式

Python基于 Gradio 和 SQLite 开发的简单博客管理平台,承受局域网手机查看,给一个PC和手机 互联方式pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: bloc…

RK3576+gc05a2

正在施工 说明Sensor 调试的第⼀个关键节点是 i2c 能否通讯成功,chip id 检查是否正确。如果是,说明上电时序没有问题。使⽤ media-ctl 获取拓扑结构,查看 Sensor 是否已经注册成⼀个 entity。如果是,说明 Sensor…

2025 年工业表面处理领域喷砂机厂家最新推荐排行榜,涵盖智能自动化可移动等类型设备优质厂家

在工业生产的表面处理环节,喷砂机的品质与性能直接关系到产品精度、生产效率及企业成本控制。当前市场上喷砂机品牌繁杂,部分品牌存在技术滞后、服务缺失、定制能力不足等问题,导致企业在选购时常常面临设备与生产需…

2025.10.14

今天学了哈佛大学的python入门课程,学习hello,name 第一个问题是发现#后边与视频中的颜色不相符,后来弄懂是主题颜色设置有关。第二个问题是在output时我的路径跟着hello name一起出来 问deep seek之后发现是需要在…

行列式按多行或列展开

在 \(n\) 阶行列式 \(D\) 中,随机选取 \(k\) 行 \(k\) 列,位于行列交叉点处的值组成的新的行列式 \(N\) ,称为 \(D\) 的一个 \(k\) 阶子式。 在 \(D\) 中划去 \(k\) 行 \(k\) 列后,剩下的 \(n-k\) 阶行列式称为余子…

2025 年化妆品代工厂最新推荐排行榜:OEM/ODM/ 私人定制等服务优选企业指南

随着美妆消费市场不断升级,新兴品牌层出不穷,化妆品代工(OEM/ODM)需求呈现爆发式增长。然而,当前代工行业鱼龙混杂,部分企业存在研发能力薄弱、品控标准缺失、服务流程混乱等问题,导致品牌方面临产品同质化严重…

SCANIA中国EDI对接供应商指南:快速完成上线的最佳方案

斯堪尼亚SCANIA中国即将推进供应商EDI对接,您准备好了吗? 随着 SCANIA中国 数字化供应链的不断深化,近期将有一批国内供应商被要求通过 EDI(Electronic Data Interchange,电子数据交换) 与其建立对接。这一举措将…

2025 年模板厂家最新推荐榜单:覆盖塑钢 / 水沟 / 现浇 / 拱形骨架等多类型,精选优质厂家助力工程高效采购

当前基建领域蓬勃发展,市政、路政、高铁等工程对模板需求激增,但市场上模板品牌繁杂,质量参差不齐,强度不足、易变形、寿命短等问题频发,严重影响工程进度与质量,给采购方带来极大困扰。为帮助采购方精准筛选适配…

RequestldleCallback

requestIdleCallback requestIdleCallback 是一个浏览器API,允许开发者在浏览器空闲时期执行后台或低优先级的任务,而不会影响关键的用户交互和动画性能。 1. 基本概念 工作原理 // 基本语法 const handle = request…