Retry для API-запросов
Автоматическое повторение упавшего fetch-запроса с экспоненциальной задержкой — для работы с нестабильными API.
Задача
Сетевые запросы иногда падают из-за временных сбоев сервера (503, таймаут). Вместо показа ошибки пользователю нужно несколько раз повторить запрос с нарастающей паузой между попытками.
Решение
// utils/retry.js
async function fetchWithRetry(url, options = {}, retries = 3, backoff = 300) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const response = await fetch(url, options);
// Повторяем только при серверных ошибках (5xx), но не клиентских (4xx)
if (response.status >= 500 && attempt < retries) {
await delay(backoff * 2 ** attempt); // 300, 600, 1200 мс
continue;
}
return response;
} catch (err) {
// Сетевая ошибка (нет соединения)
if (attempt === retries) throw err;
await delay(backoff * 2 ** attempt);
}
}
}
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export { fetchWithRetry };
Использование:
import { fetchWithRetry } from './utils/retry.js';
// До 3 повторных попыток, начальная задержка 300 мс
const response = await fetchWithRetry('/api/data', {}, 3, 300);
const data = await response.json();
TypeScript-версия с типами и jitter:
interface RetryOptions {
retries?: number;
backoff?: number;
jitter?: boolean; // добавить случайный разброс задержки
}
async function fetchWithRetry(
url: string,
fetchOptions: RequestInit = {},
{ retries = 3, backoff = 300, jitter = true }: RetryOptions = {}
): Promise<Response> {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const res = await fetch(url, fetchOptions);
if (res.status >= 500 && attempt < retries) {
await delay(calcDelay(backoff, attempt, jitter));
continue;
}
return res;
} catch (err) {
if (attempt === retries) throw err;
await delay(calcDelay(backoff, attempt, jitter));
}
}
throw new Error('Unreachable');
}
function calcDelay(backoff: number, attempt: number, jitter: boolean): number {
const base = backoff * 2 ** attempt;
return jitter ? base * (0.5 + Math.random * 0.5) : base;
}
function delay(ms: number) { return new Promise((r) => setTimeout(r, ms)); }
Ключевые моменты
- Повторяй только
5xx(серверные) —4xx(клиентские) повторять бессмысленно;401/403не станут200от повтора. - Exponential backoff — каждая попытка ждёт вдвое дольше предыдущей: 300, 600, 1200 мс.
- Jitter — случайный разброс задержки предотвращает «гром стада»: одновременный retry тысяч клиентов.
- Устанавливай
AbortControllertimeout чтобы сам fetch не висел бесконечно.
Варианты
ky— fetch-обёртка сretryопцией из коробки:ky.get(url, { retry: 3 }).axios-retry— плагин для axios.