Как реализовать повтор асинхронной операции при ошибке?

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Сложный
#JavaScript #Асинхронность

Краткий ответ

Повтор асинхронной операции — это паттерн, при котором неудачно завершившаяся асинхронная операция автоматически выполняется повторно через определенные промежутки времени. Это особенно полезно при работе с сетевыми запросами, которые могут временно завершаться ошибкой из-за проблем с сетью, сервером или перегрузки.

Основные подходы:

  • Фиксированная задержка — одинаковый интервал между попытками
  • Экспоненциальная отмена — увеличение интервала между попытками
  • Ограничение попыток — максимальное количество повторов

Полный ответ

Реализация повтора асинхронных операций — важная техника для создания надежных веб-приложений. Она позволяет автоматически восстанавливаться от временных сбоев без участия пользователя.

Основные концепции повтора

При реализации повтора необходимо учитывать несколько важных параметров:

// Базовая функция повтора
async function retryOperation(operation, maxRetries = 3) {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      if (i === maxRetries) throw error;
      // Пауза перед следующей попыткой
      await delay(1000 * Math.pow(2, i));
    }
  }
}

Типы стратегий повтора

1. Фиксированная задержка

Одинаковый интервал между всеми попытками:

function fixedDelayRetry(operation, retries, delayMs) {
  // Повтор с фиксированной задержкой
}

2. Экспоненциальная отмена

Интервал увеличивается в геометрической прогрессии:

// Задержки: 1s, 2s, 4s, 8s...
const delay = 1000 * Math.pow(2, attempt);

3. С дрожанием (Jitter)

Добавление случайности для распределения нагрузки:

// Дрожание уменьшает конкуренцию
const jitter = Math.random() * maxJitter;

Практические примеры

Простой повтор HTTP-запроса

async function fetchWithRetry(url, options = {}) {
  const { maxRetries = 3, retryDelay = 1000 } = options;
  
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Request failed');
      return response;
    } catch (error) {
      if (i === maxRetries) throw error;
      await new Promise(resolve => 
        setTimeout(resolve, retryDelay * Math.pow(2, i))
      );
    }
  }
}

Умный повтор с условиями

function smartRetry(operation, config) {
  const {
    maxRetries = 3,
    retryableErrors = ['NetworkError', 'TimeoutError']
  } = config;
  
  // Повтор только для определенных ошибок
}

Распространенные ошибки

1. Бесконечный повтор

// ❌ Может привести к блокировке
while (true) {
  try {
    await operation();
    break;
  } catch (error) {
    // Нет ограничения попыток
  }
}
 
// ✅ С ограничением попыток
for (let i = 0; i < maxRetries; i++) {
  // Логика повтора
}

2. Повтор всех ошибок

// ❌ Повтор даже фатальных ошибок
if (error.status === 404) {
  // Ресурс не существует, повтор бесполезен
}
 
// ✅ Повтор только временных ошибок
if (error.status >= 500 || error.status === 429) {
  // Серверные ошибки и ограничение частоты
}

Лучшие практики

  1. Ограничивайте количество попыток — избегайте бесконечных циклов
  2. Используйте экспоненциальную отмену — уменьшите нагрузку на сервер
  3. Добавляйте дрожание — распределите нагрузку во времени
  4. Проверяйте типы ошибок — повторяйте только временные ошибки
  5. Логируйте попытки — для отладки и мониторинга

Совместимость

Реализация повтора работает во всех современных средах JavaScript, включая браузеры и Node.js.

Ключевые преимущества повтора

  1. Устойчивость — восстановление от временных сбоев
  2. Удобство пользователя — автоматическое восстановление
  3. Надежность — уменьшение количества ошибок
  4. Производительность — оптимизация сетевых запросов
  5. Масштабируемость — управление нагрузкой на сервер

Реализация повтора асинхронных операций — важный паттерн для создания надежных веб-приложений, который помогает справляться с временными сетевыми проблемами и серверными ошибками без участия пользователя.


Задача для проверки знаний

Задача

Какой будет результат выполнения этого кода и как его улучшить?

async function unreliableOperation() {
  if (Math.random() < 0.7) {
    throw new Error('Network error');
  }
  return 'Success';
}
 
async function retry(func, times) {
  try {
    return await func();
  } catch (error) {
    if (times <= 0) throw error;
    return retry(func, times - 1);
  }
}
 
retry(unreliableOperation, 3);
Посмотреть ответ

Ответ: Результат зависит от случайного фактора, но в большинстве случаев будет ошибка “Network error”, так как отсутствует задержка между попытками.

Проблемы в коде:

  1. Нет задержки между попытками — повторы происходят мгновенно
  2. Нет экспоненциальной отмены — нагрузка на сервер остается постоянной
  3. Нет дрожания — все клиенты будут повторять одновременно
  4. Нет проверки типа ошибок — повторяются все ошибки

Улучшенная версия:

async function improvedRetry(func, maxRetries = 3) {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      return await func();
    } catch (error) {
      // Повтор только при достижении максимального количества попыток
      if (i === maxRetries) throw error;
      
      // Экспоненциальная задержка с дрожанием
      const delay = 1000 * Math.pow(2, i);
      const jitter = Math.random() * 500;
      await new Promise(resolve => 
        setTimeout(resolve, delay + jitter)
      );
    }
  }
}

Объяснение: Улучшенная версия добавляет экспоненциальную задержку и дрожание, что предотвращает одновременные повторы от множества клиентов и уменьшает нагрузку на сервер. Также она предоставляет более предсказуемое поведение и лучшую обработку ошибок.


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪