JS代码实现如下
- 简单易用:只需传入总时长,系统自动计算最优重试次数
- 精确控制:完全按照图片中的时间序列执行
- 灵活配置:支持总时长和重试次数两种配置方式
/**
* 重试回调机制处理工具
*
* 实现指数退避重试策略,解决网络不稳定、服务器临时故障等问题
*
* 核心特性:
* - 快速响应:初始重试间隔短(15秒),快速恢复正常服务
* - 逐步退避:重试间隔指数增长,避免对故障服务造成额外压力
* - 稳定重复:达到最大间隔后保持稳定,持续尝试恢复
* - 资源节约:通过抖动机制避免多个客户端同时重试造成的系统过载
*
* 重试时间序列示例:
* 15s → 30s → 60s → 120s → 240s → 480s → 960s → 1800s → 3600s → 21600s(6h) → 21600s...
*
* 适用场景:
* - API调用失败重试
* - 文件上传/下载重试
* - 数据库连接重试
* - 第三方服务调用重试
* - 网络请求超时重试
*
*/
/**
* 重试配置选项
* @typedef {Object} RetryOptions
* @property {number} [maxRetries] - 最大重试次数(可选,如果设置了totalDuration则忽略此项)
* @property {number} [totalDuration] - 总重试时长(毫秒),自动计算重试次数
* @property {number} jitter - 抖动因子(0-1),默认0.1,用于避免系统过载
* @property {boolean} enableJitter - 是否启用抖动,默认true
* @property {Function} shouldRetry - 自定义重试条件判断函数
*/
// 默认重试配置
var DEFAULT_RETRY_OPTIONS = {
maxRetries: 15, // 对应完整的重试序列长度
jitter: 0.1,
enableJitter: true,
shouldRetry: function(error, attempt) {
// 默认重试条件:网络错误、超时错误、5xx服务器错误
if (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT') {
return true;
}
if (error.response && error.response.status >= 500) {
return true;
}
return false;
}
};
/**
* 获取重试时间序列
* @returns {Array<number>} 重试时间序列(毫秒)
*/
function getDelaySequence() {
return [
15 * 1000, // 15秒
15 * 1000, // 15秒 (重复一次)
30 * 1000, // 30秒
180 * 1000, // 3分钟
600 * 1000, // 10分钟
1200 * 1000, // 20分钟
1800 * 1000, // 30分钟
1800 * 1000, // 30分钟 (重复)
1800 * 1000, // 30分钟 (重复)
3600 * 1000, // 1小时
10800 * 1000, // 3小时
10800 * 1000, // 3小时 (重复)
10800 * 1000, // 3小时 (重复)
21600 * 1000, // 6小时
21600 * 1000 // 6小时 (重复)
];
}
/**
* 根据总时长计算最大重试次数
* @param {number} totalDuration - 总重试时长(毫秒)
* @returns {number} 计算出的最大重试次数
*/
function calculateMaxRetriesFromDuration(totalDuration) {
var delaySequence = getDelaySequence();
var accumulatedTime = 0;
var maxRetries = 0;
for (var i = 0; i < delaySequence.length; i++) {
accumulatedTime += delaySequence[i];
if (accumulatedTime <= totalDuration) {
maxRetries = i + 1;
} else {
break;
}
}
// 如果总时长超过了预定义序列的总和,继续使用最后一个间隔计算
if (accumulatedTime < totalDuration && delaySequence.length > 0) {
var lastDelay = delaySequence[delaySequence.length - 1];
var remainingTime = totalDuration - accumulatedTime;
var additionalRetries = Math.floor(remainingTime / lastDelay);
maxRetries += additionalRetries;
}
return maxRetries;
}
/**
* 计算下次重试的延迟时间(预定义序列)
*
* 使用预定义的重试时间序列,实现图中描述的连续重试模式:
* 15s → 15s → 30s → 180s → 600s → 1200s → 1800s → 1800s → 1800s → 3600s → 10800s → 10800s → 10800s → 21600s → 21600s...
*
* @param {number} attempt - 当前重试次数(从0开始)
* @param {RetryOptions} options - 重试配置
* @returns {number} 延迟时间(毫秒)
*/
function calculateDelay(attempt, options) {
var jitter = options.jitter;
var enableJitter = options.enableJitter;
// 获取预定义的重试时间序列
var delaySequence = getDelaySequence();
// 获取对应序列中的延迟时间
var delay;
if (attempt < delaySequence.length) {
// 使用序列中的预定义时间
delay = delaySequence[attempt];
} else {
// 超出序列长度后,保持最后一个值(6小时)
delay = delaySequence[delaySequence.length - 1];
}
// 添加抖动,避免系统过载
if (enableJitter && jitter > 0) {
var jitterAmount = delay * jitter;
var randomJitter = (Math.random() - 0.5) * 2 * jitterAmount;
delay += randomJitter;
}
return Math.max(delay, 0);
}
/**
* 延迟执行函数
* @param {number} ms - 延迟时间(毫秒)
* @returns {Promise} Promise对象
*/
function delay(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
/**
* 重试回调机制处理函数
* @param {Function} asyncFunction - 需要重试的异步函数
* @param {RetryOptions} options - 重试配置选项
* @returns {Promise} 返回执行结果的Promise
*/
export function retryWithBackoff(asyncFunction, options) {
options = options || {};
var config = Object.assign({}, DEFAULT_RETRY_OPTIONS, options);
// 如果设置了总时长,自动计算最大重试次数
if (options.totalDuration && !options.maxRetries) {
config.maxRetries = calculateMaxRetriesFromDuration(options.totalDuration);
console.log('[重试机制] 根据总时长 ' + options.totalDuration + 'ms 计算出最大重试次数: ' + config.maxRetries);
}
var lastError;
return new Promise(function(resolve, reject) {
function attemptExecution(attempt) {
Promise.resolve(asyncFunction())
.then(function(result) {
// 成功执行,返回结果
console.log('[重试机制] 第' + (attempt + 1) + '次尝试成功');
resolve(result);
})
.catch(function(error) {
lastError = error;
// 检查是否应该重试
if (attempt === config.maxRetries || !config.shouldRetry(error, attempt)) {
console.error('[重试机制] 第' + (attempt + 1) + '次尝试失败,不再重试:', error.message);
reject(error);
return;
}
// 计算延迟时间
var delayTime = calculateDelay(attempt, config);
console.warn('[重试机制] 第' + (attempt + 1) + '次尝试失败,' + delayTime + 'ms后重试:', error.message);
// 延迟后重试
delay(delayTime).then(function() {
attemptExecution(attempt + 1);
});
});
}
attemptExecution(0);
});
}
/**
* 创建带重试机制的函数包装器
* @param {Function} asyncFunction - 需要包装的异步函数
* @param {RetryOptions} options - 重试配置选项
* @returns {Function} 包装后的函数
*/
export function withRetry(asyncFunction, options) {
return function() {
var args = Array.prototype.slice.call(arguments);
return retryWithBackoff(function() {
return asyncFunction.apply(null, args);
}, options);
};
}
/**
* 预设的重试配置
*
* 使用 totalDuration 来控制重试总时长,自动计算重试次数
* 所有预设都使用相同的重试时间序列:
* 15s → 15s → 30s → 180s → 600s → 1200s → 1800s → 1800s → 1800s → 3600s → 10800s → 10800s → 10800s → 21600s → 21600s...
*/
export var RETRY_PRESETS = {
// 快速重试:适用于轻量级操作,总时长1分钟
// 自动计算:15s + 15s + 30s = 60s,重试3次
FAST: {
totalDuration: 60 * 1000 // 1分钟
},
// 标准重试:适用于一般API调用,总时长30分钟
// 自动计算:15s + 15s + 30s + 180s + 600s + 1200s = 2040s (34分钟),重试6次
STANDARD: {
totalDuration: 30 * 60 * 1000 // 30分钟
},
// 长时间重试:适用于重要的后台任务,总时长2小时
// 自动计算到2小时内的所有重试
LONG_RUNNING: {
totalDuration: 2 * 60 * 60 * 1000 // 2小时
},
// 完整重试:使用完整序列,总时长24小时
// 包含完整的重试序列,最终稳定在6小时间隔
FULL: {
totalDuration: 24 * 60 * 60 * 1000 // 24小时
},
// 网络请求重试:专门针对网络请求,总时长1分钟
NETWORK: {
totalDuration: 60 * 1000, // 1分钟
shouldRetry: function(error) {
return error.code === 'NETWORK_ERROR' ||
error.code === 'TIMEOUT' ||
(error.response && error.response.status >= 500);
}
}
};
/**
* 重试状态管理器
*/
export function RetryManager() {
this.activeRetries = new Map();
}
RetryManager.prototype = {
/**
* 执行带重试的异步操作
* @param {string} key - 操作标识符
* @param {Function} asyncFunction - 异步函数
* @param {RetryOptions} options - 重试配置
* @returns {Promise} 执行结果
*/
execute: function(key, asyncFunction, options) {
var self = this;
// 如果已有相同key的重试在进行,返回现有的Promise
if (this.activeRetries.has(key)) {
console.log('[重试管理器] 操作"' + key + '"已在重试中,返回现有Promise');
return this.activeRetries.get(key);
}
// 创建新的重试Promise
var retryPromise = retryWithBackoff(asyncFunction, options)
.finally(function() {
// 完成后清理
self.activeRetries.delete(key);
});
this.activeRetries.set(key, retryPromise);
return retryPromise;
},
/**
* 取消指定的重试操作
* @param {string} key - 操作标识符
*/
cancel: function(key) {
if (this.activeRetries.has(key)) {
this.activeRetries.delete(key);
console.log('[重试管理器] 已取消操作"' + key + '"的重试');
}
},
/**
* 获取当前活跃的重试操作数量
* @returns {number} 活跃重试数量
*/
getActiveCount: function() {
return this.activeRetries.size;
},
/**
* 清理所有重试操作
*/
clear: function() {
this.activeRetries.clear();
console.log('[重试管理器] 已清理所有重试操作');
}
};
// 导出默认的重试管理器实例
export var defaultRetryManager = new RetryManager();
/**
* 辅助函数:根据总时长预览重试次数和时间点
* @param {number} totalDuration - 总时长(毫秒)
* @returns {Object} 包含重试次数和时间点信息
*/
export function previewRetrySchedule(totalDuration) {
var maxRetries = calculateMaxRetriesFromDuration(totalDuration);
var delaySequence = getDelaySequence();
var schedule = [];
var accumulatedTime = 0;
for (var i = 0; i < maxRetries && i < delaySequence.length; i++) {
accumulatedTime += delaySequence[i];
schedule.push({
attempt: i + 1,
delay: delaySequence[i],
delayFormatted: formatDuration(delaySequence[i]),
accumulatedTime: accumulatedTime,
accumulatedTimeFormatted: formatDuration(accumulatedTime)
});
}
// 如果超出了预定义序列,继续计算
if (maxRetries > delaySequence.length) {
var lastDelay = delaySequence[delaySequence.length - 1];
for (var j = delaySequence.length; j < maxRetries; j++) {
accumulatedTime += lastDelay;
schedule.push({
attempt: j + 1,
delay: lastDelay,
delayFormatted: formatDuration(lastDelay),
accumulatedTime: accumulatedTime,
accumulatedTimeFormatted: formatDuration(accumulatedTime)
});
}
}
return {
totalDuration: totalDuration,
totalDurationFormatted: formatDuration(totalDuration),
maxRetries: maxRetries,
schedule: schedule
};
}
/**
* 格式化时长显示
* @param {number} ms - 毫秒数
* @returns {string} 格式化后的时长字符串
*/
function formatDuration(ms) {
var seconds = Math.floor(ms / 1000);
var minutes = Math.floor(seconds / 60);
var hours = Math.floor(minutes / 60);
if (hours > 0) {
return hours + '小时' + (minutes % 60 > 0 ? (minutes % 60) + '分钟' : '');
} else if (minutes > 0) {
return minutes + '分钟' + (seconds % 60 > 0 ? (seconds % 60) + '秒' : '');
} else {
return seconds + '秒';
}
}
/**
* 使用示例:
*
* // 基础使用 - 网络请求重试1分钟总时长
* retryWithBackoff(function() {
* return fetch('/api/data');
* }, RETRY_PRESETS.NETWORK);
*
* // 标准重试 - 30分钟总时长,自动计算重试次数
* var apiCall = withRetry(function(url) {
* return fetch(url);
* }, RETRY_PRESETS.STANDARD);
*
* // 长时间重试 - 2小时总时长
* defaultRetryManager.execute('important-task', function() {
* return fetch('/api/critical-operation');
* }, RETRY_PRESETS.LONG_RUNNING);
*
* // 完整重试 - 24小时总时长,最终稳定在6小时间隔
* retryWithBackoff(function() {
* return fetch('/api/critical-system');
* }, RETRY_PRESETS.FULL);
*
* // 自定义总时长 - 重试5分钟
* retryWithBackoff(function() {
* return fetch('/api/custom-check');
* }, { totalDuration: 5 * 60 * 1000 }); // 5分钟
*
* // 仍然支持手动设置重试次数
* retryWithBackoff(function() {
* return fetch('/api/quick-check');
* }, { maxRetries: 2 });
*
* // 预览重试计划
* var schedule = previewRetrySchedule(30 * 60 * 1000); // 30分钟
* console.log('总重试次数:', schedule.maxRetries);
* console.log('重试计划:', schedule.schedule);
* // 输出示例:
* // 总重试次数: 6
* // 重试计划: [
* // { attempt: 1, delay: 15000, delayFormatted: '15秒', accumulatedTime: 15000, accumulatedTimeFormatted: '15秒' },
* // { attempt: 2, delay: 15000, delayFormatted: '15秒', accumulatedTime: 30000, accumulatedTimeFormatted: '30秒' },
* // ...
* // ]
*/