← Назад к вопросам

Можно ли перезапустить rejected Promise?

2.0 Middle🔥 211 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли «перезапустить» rejected Promise?

Короткий ответ: нет, нельзя. Promise в JavaScript, как следует из названия (promise — «обещание»), представляет собой одноразовое завершённое состояние. Как только промис перешёл в состояние fulfilled (выполнено успешно) или rejected (выполнено с ошибкой), это состояние становится неизменным (immutable). Это фундаментальное свойство промисов, гарантирующее предсказуемость асинхронного кода.

Почему нельзя изменить состояние промиса?

Спецификация ES6 определяет Promise как объект с внутренним состоянием:

  • pending (ожидание) — начальное состояние.
  • fulfilled (выполнено) — операция завершилась успешно.
  • rejected (отклонено) — операция завершилась с ошибкой.

Переход из pending в fulfilled или rejected является финальным. Попытка «перезапустить» отклонённый промис означала бы сбросить его состояние обратно в pending, что нарушило бы базовый контракт промисов и привело бы к неконсистентному поведению в цепочках обработки.

const promise = new Promise((resolve, reject) => {
  reject(new Error('Первая ошибка'));
});

// Попытка "исправить" промис извне — НЕ РАБОТАЕТ.
// Невозможно вызвать resolve/reject для уже завершённого промиса.
promise.catch(err => console.log(err.message)); // Выведет: "Первая ошибка"

// Любые последующие вызовы then/catch будут обрабатывать РЕЗУЛЬТАТ первого завершения.
promise
  .then(() => console.log('Этот then никогда не выполнится'))
  .catch(() => console.log('Этот catch получит ту же самую ошибку'));

Стратегии обработки и «перезапуска» асинхронных операций

Хотя конкретный промис перезапустить нельзя, реализовать логику повторной попытки (retry logic) выполнения исходной асинхронной задачи — не только возможно, но и является распространённой практикой. Для этого нужно создавать новый промис при каждой попытке.

1. Ручной ретрай с помощью функции-обёртки

function fetchWithRetry(url, maxRetries = 3) {
  return new Promise((resolve, reject) => {
    const attempt = (retryCount) => {
      fetch(url)
        .then(resolve) // Успех — разрешаем основной промис
        .catch((error) => {
          if (retryCount < maxRetries) {
            console.log(`Попытка ${retryCount + 1} не удалась, пробуем ещё...`);
            attempt(retryCount + 1); // Создаём новую попытку (новый промис от fetch)
          } else {
            reject(new Error(`Все ${maxRetries} попытки завершились ошибкой: ${error.message}`));
          }
        });
    };
    attempt(0);
  });
}

// Использование
fetchWithRetry('https://api.example.com/data')
  .then(data => console.log('Данные получены:', data))
  .catch(err => console.error('Окончательная ошибка:', err));

2. Использование async/await с циклом

Более читаемый и современный подход.

async function fetchWithRetryAsync(url, maxRetries = 3) {
  let lastError;
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const response = await fetch(url); // Каждый await — новая асинхронная операция
      return await response.json(); // Успех — возвращаем результат
    } catch (error) {
      lastError = error;
      console.log(`Попытка ${i + 1} не удалась.`);
      if (i < maxRetries) {
        // Часто добавляют задержку перед повторной попыткой (exponential backoff)
        await new Promise(res => setTimeout(res, 1000 * Math.pow(2, i)));
      }
    }
  }
  throw lastError; // Все попытки исчерпаны
}

3. Готовые библиотечные решения

Для сложных сценариев (например, с экспоненциальной задержкой или обработкой специфических ошибок) используют библиотеки:

  • p-retry — популярная специализированная библиотека.
  • axios-retry — для клиента Axios.
  • fetch-retry — для Fetch API.
import pRetry from 'p-retry';

const run = async () => {
  const response = await fetch('https://api.example.com/data');
  return response.json();
};

// pRetry автоматически повторит выполнение функции run при ошибках
const data = await pRetry(run, { retries: 5, onFailedAttempt: console.log });

Ключевые выводы:

  • Promise — final state. Один конкретный инстанс промиса не может изменить своё состояние после fulfilled или rejected.
  • Логика повторения — на уровне бизнес-логики. Вы реализуете не «перезапуск промиса», а повторный вызов исходной асинхронной функции, которая каждый раз возвращает новый промис.
  • Обработчики — не изменяют промис. Методы .catch() или .then() не «чинят» исходный отклонённый промис. Они возвращают новый промис, основанный на результате работы своего коллбэка. Это позволяет строить цепочки обработки ошибок и преобразования результатов.
  • Паттерн Retry — обязателен для надёжных приложений. Корректная обработка сетевых сбоев, временной недоступности API и других неустойчивых операций — признак зрелости фронтенд-разработчика.

Таким образом, правильная философия работы с ошибками промисов заключается не в попытках изменить неизменяемое, а в проектировании кода, который способен безопасно обрабатывать неудачи и инициировать новые попытки выполнения задачи при необходимости.

Можно ли перезапустить rejected Promise? | PrepBro