Files
qiming/qiming-mobile/uni_modules/x-tools/tools/retryAsync.js

112 lines
3.3 KiB
JavaScript

/**
* 异步操作重试工具函数
* @param {Function} asyncFn - 需要重试的异步函数
* @param {Object} options - 重试配置选项
* @param {number} options.maxRetries - 最大重试次数 (默认: 3)
* @param {number} options.delay - 重试延迟时间(ms) (默认: 1000)
* @param {string} options.backoffStrategy - 退避策略 ('fixed' | 'exponential' | 'linear') (默认: 'fixed')
* @param {number} options.backoffFactor - 退避因子 (默认: 2)
* @param {Function} options.shouldRetry - 自定义重试条件函数 (默认: 所有错误都重试)
* @param {Function} options.onRetry - 重试回调函数
* @param {number} options.timeout - 单次操作超时时间(ms)
* @returns {Promise} 返回异步操作的结果
*/
export async function retryAsync(asyncFn, options = {}) {
const {
maxRetries = 3,
delay = 1000,
backoffStrategy = 'fixed',
backoffFactor = 2,
shouldRetry = () => true,
onRetry = () => {},
timeout
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
// 如果设置了超时时间,包装函数添加超时控制
const result = timeout ?
await withTimeout(asyncFn, timeout) :
await asyncFn();
return result;
} catch (error) {
lastError = error;
// 如果是最后一次尝试,直接抛出错误
if (attempt === maxRetries) {
throw error;
}
// 检查是否应该重试
if (!shouldRetry(error, attempt + 1)) {
throw error;
}
// 执行重试回调
onRetry(error, attempt + 1, maxRetries);
// 计算延迟时间并等待
const waitTime = calculateDelay(delay, attempt, backoffStrategy, backoffFactor);
await sleep(waitTime);
}
}
throw lastError;
}
/**
* 计算重试延迟时间
*/
function calculateDelay(baseDelay, attempt, strategy, factor) {
switch (strategy) {
case 'exponential':
return baseDelay * Math.pow(factor, attempt);
case 'linear':
return baseDelay * (attempt + 1);
case 'fixed':
default:
return baseDelay;
}
}
/**
* 延迟函数
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 为异步函数添加超时控制
*/
function withTimeout(asyncFn, timeoutMs) {
return new Promise((resolve, reject) => {
let isResolved = false;
const timer = setTimeout(() => {
if (!isResolved) {
isResolved = true;
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
}
}, timeoutMs);
asyncFn()
.then(result => {
if (!isResolved) {
isResolved = true;
clearTimeout(timer);
resolve(result);
}
})
.catch(error => {
if (!isResolved) {
isResolved = true;
clearTimeout(timer);
reject(error);
}
});
});
}