Retry Pattern
Retry Pattern — паттерн устойчивости (resilience), автоматически повторяющий асинхронную операцию при временной ошибке, с поддержкой экспоненциальной задержки (backoff) и максимального числа попыток.
Зачем нужно
Сетевые запросы, обращения к базе данных и внешним API могут временно завершаться ошибкой (429 Rate Limit, 503 Service Unavailable, сетевой таймаут). Retry Pattern автоматически обрабатывает такие временные сбои, не требуя вмешательства пользователя, при этом не перегружая сервис через экспоненциальный backoff.
Где используется
- HTTP-запросы к нестабильным API
- Операции с базой данных при временной недоступности
- Загрузка файлов с ненадёжным соединением
- Микросервисная архитектура (circuit breaker + retry)
- Очереди задач (повторная обработка при сбое)
Основной контент
Базовая реализация
async function retry(fn, maxAttempts = 3, delay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn;
} catch (error) {
lastError = error;
console.warn(`Попытка ${attempt}/${maxAttempts} неудачна:`, error.message);
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Все ${maxAttempts} попытки неудачны. Последняя ошибка: ${lastError.message}`);
}
// Использование
const data = await retry(
=> fetch('https://api.example.com/data').then(r => r.json()),
3,
1000
);
Экспоненциальный backoff с jitter
async function retryWithBackoff(fn, options = {}) {
const {
maxAttempts = 3,
baseDelay = 1000,
maxDelay = 30000,
factor = 2,
jitter = true
} = options;
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn(attempt);
} catch (error) {
lastError = error;
// Не повторять для клиентских ошибок (4xx кроме 429)
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
throw error;
}
if (attempt < maxAttempts) {
// Экспоненциальный рост: 1s, 2s, 4s, 8s...
let delay = Math.min(baseDelay * Math.pow(factor, attempt - 1), maxDelay);
// Jitter — случайное смещение для предотвращения thundering herd
if (jitter) {
delay = delay * (0.5 + Math.random * 0.5);
}
console.log(`Повтор через ${Math.round(delay)}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
// Использование
const result = await retryWithBackoff(
async (attempt) => {
const response = await fetch('/api/data');
if (!response.ok) {
const err = new Error(`HTTP ${response.status}`);
err.status = response.status;
throw err;
}
return response.json();
},
{ maxAttempts: 5, baseDelay: 500, maxDelay: 10000 }
);
Retry с условием повтора
async function retryIf(fn, shouldRetry, maxAttempts = 3) {
for (let i = 1; i <= maxAttempts; i++) {
try {
return await fn;
} catch (error) {
if (i === maxAttempts || !shouldRetry(error, i)) {
throw error;
}
await new Promise(r => setTimeout(r, 1000 * i));
}
}
}
// Повторять только при сетевых ошибках и 503
const isRetryable = (err) =>
err.name === 'NetworkError' || err.status === 503 || err.status === 429;
await retryIf( => fetchData, isRetryable, 4);
Частые ошибки
- Повторять не-временные ошибки — 404, 400 — не временные сбои. Повторный запрос не поможет. Фильтруйте ошибки через
shouldRetry. - Нет задержки между попытками — немедленный повтор перегружает сервер. Всегда используйте delay, в идеале — exponential backoff с jitter.
- Нет максимального числа попыток — бесконечный retry может зависнуть навсегда. Всегда ограничивайте
maxAttempts.