Spaces:
Sleeping
Sleeping
| 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 }; | |