Spaces:
Sleeping
Sleeping
File size: 2,809 Bytes
ccb6b75 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import logger from './logger.js';
export async function withRetry(fn, options = {}) {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 30000,
jitter = true,
retryableErrors = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'],
onRetry = null,
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
const isRetryable = _isRetryableError(error, retryableErrors);
if (!isRetryable || attempt === maxRetries) {
throw error;
}
const delay = _calculateDelay(attempt, baseDelay, maxDelay, jitter);
logger.warn(
`Retry ${attempt + 1}/${maxRetries} after ${delay}ms: ${error.message}`
);
if (onRetry) {
await onRetry(error, attempt, delay);
}
await _sleep(delay);
}
}
throw lastError;
}
export async function withTimeout(promise, ms, error = null) {
let timeoutId;
const timeout = new Promise((_, reject) => {
timeoutId = setTimeout(
() => reject(error || new Error(`Operation timed out after ${ms}ms`)),
ms
);
});
try {
return await Promise.race([promise, timeout]);
} finally {
clearTimeout(timeoutId);
}
}
export async function withRateLimit(fn, options = {}) {
const {
maxRetries = 5,
baseDelay = 2000,
maxDelay = 120000,
} = options;
return withRetry(fn, {
maxRetries,
baseDelay,
maxDelay,
retryableErrors: ['RATE_LIMITED', '403', '429'],
onRetry: (error, attempt, delay) => {
if (error.status === 403 || error.status === 429) {
const resetTime = error.response?.headers?.['x-ratelimit-reset'];
if (resetTime) {
const waitTime = (parseInt(resetTime) * 1000) - Date.now();
if (waitTime > 0 && waitTime < maxDelay) {
logger.info(`Rate limit reset in ${Math.round(waitTime / 1000)}s`);
}
}
}
},
});
}
function _isRetryableError(error, retryableErrors) {
if (!error) return false;
if (error.code && retryableErrors.includes(error.code)) return true;
if (error.status && (error.status >= 500 || error.status === 429)) return true;
if (error.message && retryableErrors.some(r => error.message.includes(r))) return true;
return false;
}
function _calculateDelay(attempt, baseDelay, maxDelay, jitter) {
const exponential = baseDelay * Math.pow(2, attempt);
const delay = Math.min(exponential, maxDelay);
if (jitter) {
const jitterAmount = delay * 0.1;
return delay + (Math.random() - 0.5) * 2 * jitterAmount;
}
return delay;
}
function _sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export default { withRetry, withTimeout, withRateLimit };
|