238 lines
6.5 KiB
JavaScript
238 lines
6.5 KiB
JavaScript
/**
|
|
* API请求重试机制
|
|
* 提供自动重试功能和UI反馈
|
|
*/
|
|
|
|
import { ElMessage, ElNotification } from 'element-plus'
|
|
|
|
// 重试配置
|
|
const retryConfig = {
|
|
// 默认最大重试次数
|
|
defaultMaxRetries: 3,
|
|
|
|
// 默认重试延迟(毫秒)
|
|
defaultRetryDelay: 1000,
|
|
|
|
// 指数退避因子
|
|
backoffFactor: 2,
|
|
|
|
// 最大重试延迟(毫秒)
|
|
maxRetryDelay: 10000,
|
|
|
|
// 需要重试的HTTP状态码
|
|
retryableStatusCodes: [408, 429, 500, 502, 503, 504],
|
|
|
|
// 需要重试的网络错误类型
|
|
retryableErrorTypes: ['NETWORK_ERROR', 'TIMEOUT', 'SERVER_ERROR']
|
|
}
|
|
|
|
/**
|
|
* 计算重试延迟时间
|
|
* @param {number} retryCount - 当前重试次数
|
|
* @param {number} baseDelay - 基础延迟时间
|
|
* @returns {number} 计算后的延迟时间
|
|
*/
|
|
function calculateRetryDelay(retryCount, baseDelay = retryConfig.defaultRetryDelay) {
|
|
const delay = baseDelay * Math.pow(retryConfig.backoffFactor, retryCount - 1)
|
|
return Math.min(delay, retryConfig.maxRetryDelay)
|
|
}
|
|
|
|
/**
|
|
* 判断错误是否可重试
|
|
* @param {Error} error - 错误对象
|
|
* @returns {boolean} 是否可重试
|
|
*/
|
|
function isRetryableError(error) {
|
|
// 检查是否是网络错误
|
|
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT') {
|
|
return true
|
|
}
|
|
|
|
// 检查HTTP状态码
|
|
if (error.response && error.response.status) {
|
|
return retryConfig.retryableStatusCodes.includes(error.response.status)
|
|
}
|
|
|
|
// 检查错误类型
|
|
if (error.type && retryConfig.retryableErrorTypes.includes(error.type)) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* 显示重试通知
|
|
* @param {string} operation - 操作描述
|
|
* @param {number} retryCount - 当前重试次数
|
|
* @param {number} maxRetries - 最大重试次数
|
|
* @param {number} delay - 延迟时间
|
|
* @returns {Object} 通知对象
|
|
*/
|
|
function showRetryNotification(operation, retryCount, maxRetries, delay) {
|
|
const message = `${operation} 失败,正在第 ${retryCount}/${maxRetries} 次重试...`
|
|
|
|
return ElNotification({
|
|
title: '请求重试',
|
|
message,
|
|
type: 'warning',
|
|
duration: delay,
|
|
showClose: false
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 显示重试失败通知
|
|
* @param {string} operation - 操作描述
|
|
* @param {Error} error - 错误对象
|
|
*/
|
|
function showRetryFailedNotification(operation, error) {
|
|
ElMessage.error({
|
|
message: `${operation} 失败,已达到最大重试次数: ${error.message || '未知错误'}`,
|
|
duration: 5000
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 带重试机制的请求函数
|
|
* @param {Function} requestFn - 请求函数
|
|
* @param {Object} options - 重试选项
|
|
* @param {string} options.operation - 操作描述
|
|
* @param {number} options.maxRetries - 最大重试次数
|
|
* @param {number} options.retryDelay - 基础重试延迟
|
|
* @param {Function} options.onRetry - 重试回调
|
|
* @param {Function} options.onFinalError - 最终错误回调
|
|
* @returns {Promise} 请求结果
|
|
*/
|
|
export async function retryRequest(requestFn, options = {}) {
|
|
const {
|
|
operation = '请求',
|
|
maxRetries = retryConfig.defaultMaxRetries,
|
|
retryDelay = retryConfig.defaultRetryDelay,
|
|
onRetry,
|
|
onFinalError
|
|
} = options
|
|
|
|
let lastError = null
|
|
let retryCount = 0
|
|
|
|
// 第一次尝试
|
|
try {
|
|
return await requestFn()
|
|
} catch (error) {
|
|
lastError = error
|
|
|
|
// 如果错误不可重试,直接抛出
|
|
if (!isRetryableError(error)) {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// 重试循环
|
|
for (retryCount = 1; retryCount <= maxRetries; retryCount++) {
|
|
const delay = calculateRetryDelay(retryCount, retryDelay)
|
|
|
|
// 显示重试通知
|
|
const notification = showRetryNotification(operation, retryCount, maxRetries, delay)
|
|
|
|
// 调用重试回调
|
|
if (onRetry) {
|
|
onRetry(retryCount, maxRetries, delay, lastError)
|
|
}
|
|
|
|
// 等待延迟
|
|
await new Promise(resolve => setTimeout(resolve, delay))
|
|
|
|
// 关闭通知
|
|
notification.close()
|
|
|
|
try {
|
|
// 执行重试请求
|
|
const result = await requestFn()
|
|
|
|
// 重试成功,显示成功消息
|
|
ElMessage.success({
|
|
message: `${operation} 在第 ${retryCount} 次重试后成功`,
|
|
duration: 3000
|
|
})
|
|
|
|
return result
|
|
} catch (error) {
|
|
lastError = error
|
|
|
|
// 如果错误不可重试或已达到最大重试次数,跳出循环
|
|
if (!isRetryableError(error) || retryCount === maxRetries) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// 所有重试都失败
|
|
showRetryFailedNotification(operation, lastError)
|
|
|
|
// 调用最终错误回调
|
|
if (onFinalError) {
|
|
onFinalError(lastError, retryCount)
|
|
}
|
|
|
|
throw lastError
|
|
}
|
|
|
|
/**
|
|
* 创建带重试机制的API请求函数
|
|
* @param {Function} apiRequest - API请求函数
|
|
* @param {Object} retryOptions - 重试选项
|
|
* @returns {Function} 带重试机制的请求函数
|
|
*/
|
|
export function createRetryableRequest(apiRequest, retryOptions = {}) {
|
|
return async function(...args) {
|
|
return retryRequest(() => apiRequest(...args), retryOptions)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 为axios实例添加重试拦截器
|
|
* @param {Object} axiosInstance - axios实例
|
|
* @param {Object} options - 重试选项
|
|
*/
|
|
export function addRetryInterceptor(axiosInstance, options = {}) {
|
|
axiosInstance.interceptors.response.use(
|
|
response => response,
|
|
async error => {
|
|
const config = error.config
|
|
|
|
// 如果没有配置对象或已禁用重试,直接抛出错误
|
|
if (!config || config.disableRetry === true) {
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
// 获取重试配置
|
|
const maxRetries = config.maxRetries || options.maxRetries || retryConfig.defaultMaxRetries
|
|
const retryDelay = config.retryDelay || options.retryDelay || retryConfig.defaultRetryDelay
|
|
const operation = config.operation || options.operation || 'API请求'
|
|
|
|
// 初始化重试计数
|
|
config.retryCount = config.retryCount || 0
|
|
|
|
// 如果已达到最大重试次数或错误不可重试,直接抛出错误
|
|
if (config.retryCount >= maxRetries || !isRetryableError(error)) {
|
|
return Promise.reject(error)
|
|
}
|
|
|
|
// 增加重试计数
|
|
config.retryCount += 1
|
|
|
|
// 计算延迟时间
|
|
const delay = calculateRetryDelay(config.retryCount, retryDelay)
|
|
|
|
// 显示重试通知
|
|
showRetryNotification(operation, config.retryCount, maxRetries, delay)
|
|
|
|
// 等待延迟
|
|
await new Promise(resolve => setTimeout(resolve, delay))
|
|
|
|
// 重新发起请求
|
|
return axiosInstance(config)
|
|
}
|
|
)
|
|
} |