async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Don't retry client errors (except 429)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
return response;
}
// Retry rate limits
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "5");
await sleep(retryAfter * 1000);
continue;
}
// Retry server errors
if (response.status >= 500) {
if (attempt < maxRetries) {
await sleep(getBackoff(attempt));
continue;
}
}
return response;
} catch (err) {
// Network error — retry
if (attempt < maxRetries) {
await sleep(getBackoff(attempt));
continue;
}
throw err;
}
}
}
function getBackoff(attempt) {
const base = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s, 8s...
const jitter = Math.random() * 1000; // 0-1s random jitter
return base + jitter;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}