请求超时重试封装
请求超时重试封装
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 {
// 创建超时 Promise
const 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 signal
if (typeof requestFn === 'function') {
const requestResult = requestFn();
// 检查是否是 fetch 风格的请求
if (requestResult && typeof (requestResult as any).catch === 'function') {
// 普通 Promise
result = 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>
);
}
主要特性
- 多种重试策略: 指数退避、固定间隔、线性增长
- 智能重试条件: 网络错误、超时、5xx状态码自动重试
- 超时控制: 单次请求超时和总超时控制
- 事件监听: 重试、成功、失败事件回调
- 随机抖动: 避免惊群效应
- TypeScript 支持: 完整的类型定义
- 框架集成: 支持 Axios、Fetch、React Hook
这个封装提供了灵活的重试机制,可以根据具体需求调整重试策略和参数,有效处理网络不稳定的情况。
挣钱养家

浙公网安备 33010602011771号