当前位置: 首页 > news >正文

请求超时重试封装

请求超时重试封装

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.hskmm.com/?act=detail&tid=30831

相关文章:

  • Emacs常用的一些快捷键,记不住的,方便查询!!
  • Microsoft Visual C++,Microsoft Visual Studio for Office Runtime,Microsoft Visual Basic Runtime等下载
  • 2025 年耐热钢厂家及热处理工装设备厂家推荐榜:多用炉/真空炉/台车炉/井式炉/箱式炉/耐热钢工装厂家,聚焦高效适配,助力企业精准选型
  • 实用指南:如何进行WGBS的数据挖掘——从甲基化水平到功能通路
  • python对接印度尼西亚股票数据接口文档
  • Webpack优化
  • 2025年舒适轮胎厂家最新权威推荐榜:静音耐磨,驾驶体验全面升级!
  • 2025年耐磨轮胎厂家最新推荐排行榜,矿山耐磨轮胎,工程耐磨轮胎,重载耐磨轮胎公司推荐!
  • Map做数据缓存
  • Python基于 Gradio 和 SQLite 开发的简单博客管理平台,承受局域网手机查看,给一个PC和手机 互联方式
  • RK3576+gc05a2
  • 2025 年工业表面处理领域喷砂机厂家最新推荐排行榜,涵盖智能自动化可移动等类型设备优质厂家
  • 2025.10.14
  • 行列式按多行或列展开
  • 2025 年化妆品代工厂最新推荐排行榜:OEM/ODM/ 私人定制等服务优选企业指南
  • SCANIA中国EDI对接供应商指南:快速完成上线的最佳方案
  • 2025 年模板厂家最新推荐榜单:覆盖塑钢 / 水沟 / 现浇 / 拱形骨架等多类型,精选优质厂家助力工程高效采购
  • RequestldleCallback
  • 前端开发调试实战指南,从浏览器到 WebView 的全链路问题排查思路
  • 基于EKF/UKF的非线性飞行器系统滤波实现
  • go-基于 Prometheus 的全方位食用手册 - fox
  • 实验任务2 - pp
  • 插入公式总是有个框框
  • picard标记DI/DS标签
  • 2025年成都全日制辅导机构优选指南,全日制培训班/集训机构/集训班/全日制一对一培训/文化课集训机构,学习提升新选择
  • 2025 年灭老鼠公司最新推荐排行榜:欧盟认证技术与环保服务双优品牌权威甄选,含成都 / 四川专业机构口碑指南除老鼠/消灭老鼠/老鼠消杀公司推荐
  • uni-app x初探
  • 深度SEO优化的方式有哪些,从技术层面来说
  • 2025 年南昌装修公司推荐南昌市宿然装饰工程有限公司,以专业与真诚雕琢理想空间南昌装修设计推荐指南!
  • C# Avalonia 16- Animation- AnimateRadialGradient