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

Нужно ли делать отдельный catch для каждого then в Promise?

2.3 Middle🔥 271 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Нужно ли делать отдельный catch для каждого then?

Нет, обычно не нужно. Один catch в конце цепи Promise обработает ошибки со всех предыдущих .then(). Это одно из самых полезных свойств Promise цепочек.

Как работает обработка ошибок в Promise цепи

Один catch на конце (правильно)

fetch('/api/users/1')
  .then(response => response.json())
  .then(user => {
    console.log('User loaded:', user.name);
    return user;
  })
  .then(user => updateUI(user))
  .catch(error => {
    // Обработает ошибку с ЛЮБОГО из предыдущих .then()
    console.error('Error:', error.message);
  });

Что перехватит этот catch:

  1. Ошибка в fetch (сеть недоступна)
  2. Ошибка парсинга JSON
  3. Ошибка в логировании (если user.name undefined)
  4. Ошибка в updateUI

Catch обрабатывает как Promise reject, так и обычные ошибки

Promise.resolve()
  .then(() => {
    // Обычная ошибка в .then
    throw new Error('Что-то пошло не так!');
  })
  .then(() => {
    console.log('Это не выполнится');
  })
  .catch(error => {
    // Перехватит Error выше
    console.error('Caught:', error.message); // "Что-то пошло не так!"
  });

Когда нужны отдельные catch?

1. Разные действия для разных ошибок

fetch('/api/users/1')
  .then(response => {
    if (response.status === 404) {
      throw new NotFoundError('User not found');
    }
    return response.json();
  })
  .catch(error => {
    // Обработаем NOT FOUND специально
    if (error instanceof NotFoundError) {
      redirectTo('/users');
      return null; // Можно вернуть default значение
    }
    throw error; // Пробросить дальше
  })
  .then(user => {
    if (user !== null) {
      updateUI(user);
    }
  })
  .catch(error => {
    // Общая обработка других ошибок
    showError('Failed to load user');
  });

2. Восстановление после ошибки

fetch('/api/users/1')
  .then(response => response.json())
  .catch(error => {
    // Пытаемся загрузить из кэша
    console.log('API failed, using cache');
    return getFromCache('users/1');
  })
  .then(user => {
    updateUI(user);
  })
  .catch(error => {
    // Если и кэш не помог
    showError('No data available');
  });

3. Промежуточная обработка

async function getUserAndPosts(userId) {
  return fetch(`/api/users/${userId}`)
    .then(r => r.json())
    .catch(error => {
      // Если не удалось загрузить юзера
      logger.error('User loading failed:', error);
      throw error; // Пробросить выше
    })
    .then(user => {
      // Теперь загружаем посты
      return fetch(`/api/users/${user.id}/posts`)
        .then(r => r.json())
        .catch(error => {
          // Если не удалось загрузить посты
          logger.error('Posts loading failed:', error);
          return []; // Вернуть пустой массив
        })
        .then(posts => ({ user, posts }));
    });
}

Практический пример: API запрос

Плохо: catch для каждого then

fetch('/api/data')
  .then(r => r.json())
  .catch(error => console.error('Parse error:', error)) // Избыточно
  .then(data => processData(data))
  .catch(error => console.error('Process error:', error)) // Избыточно
  .then(result => displayResult(result))
  .catch(error => console.error('Display error:', error)); // Избыточно

Хорошо: один catch на конце

fetch('/api/data')
  .then(r => r.json())
  .then(data => processData(data))
  .then(result => displayResult(result))
  .catch(error => {
    // Единая обработка ВСЕХ ошибок
    console.error('Operation failed:', error);
    showErrorNotification(error);
  });

Лучшее: с промежуточной обработкой

fetch('/api/data')
  .then(r => r.json())
  .catch(error => {
    // Специальная обработка JSON ошибок
    if (error instanceof SyntaxError) {
      console.error('Invalid JSON response');
    }
    throw error;
  })
  .then(data => processData(data))
  .then(result => displayResult(result))
  .catch(error => {
    // Финальная обработка
    showErrorNotification('Failed to load data');
  });

Сравнение с async/await (рекомендуется)

Основная проблема Promise цепочек - сложность. async/await чище:

// Promise цепь
fetch('/api/data')
  .then(r => r.json())
  .then(data => processData(data))
  .then(result => displayResult(result))
  .catch(error => {
    console.error('Error:', error);
  });

// async/await (чище!)
async function loadData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    const result = await processData(data);
    displayResult(result);
  } catch (error) {
    console.error('Error:', error);
  }
}

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

Ошибка 1: Забыть пробросить ошибку

// Неправильно: catch молча игнорирует ошибку
fetch('/api/data')
  .then(r => r.json())
  .catch(error => {
    console.log('Error occurred'); // Логируем, но не пробрасываем
  })
  .then(data => {
    // data будет undefined!
    processData(data); // Будет ошибка
  });

// Правильно: пробросить или вернуть значение
.catch(error => {
  console.log('Error occurred');
  throw error; // Или вернуть default: return null;
})

Ошибка 2: Забыть return в .then

// Неправильно: цепь разрывается
fetch('/api/users')
  .then(r => r.json())
  .then(users => {
    users.forEach(user => console.log(user.name));
    // Не вернули значение!
  })
  .then(result => {
    console.log(result); // undefined
  });

// Правильно
.then(users => {
  console.log(users.length);
  return users; // Вернуть для следующего .then
})

Ошибка 3: Забыть return в .catch

// Неправильно
fetch('/api/data')
  .catch(error => {
    logger.error(error);
    // Не пробросили, цепь продолжит с undefined
  })
  .then(data => {
    console.log(data); // undefined!
  });

// Правильно
.catch(error => {
  logger.error(error);
  return { default: 'data' }; // Вернуть default или пробросить
  // throw error; // Альтернатива
})

Правила обработки ошибок

1. Один catch в конце для разных ошибок с одной обработкой

fetch('/api/data')
  .then(r => r.json())
  .then(processData)
  .then(displayUI)
  .catch(error => showErrorModal(error));

2. Отдельные catch для специальной обработки

fetch('/api/data')
  .then(r => r.json())
  .catch(parseError => {
    // Специальная обработка ошибок парсинга
    logger.error('JSON parse error');
    throw parseError;
  })
  .then(processData)
  .catch(processError => {
    // Специальная обработка ошибок обработки
    logger.error('Processing failed');
    throw processError;
  })
  .then(displayUI)
  .catch(displayError => {
    // Финальная обработка
    showErrorModal(displayError);
  });

3. Лучшее решение: async/await

async function loadAndDisplay() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    const processed = await processData(data);
    displayUI(processed);
  } catch (error) {
    if (error instanceof SyntaxError) {
      logger.error('JSON parse error');
    } else if (error instanceof ProcessError) {
      logger.error('Processing failed');
    } else {
      logger.error('Unexpected error');
    }
    showErrorModal(error);
  }
}

Checklist: Обработка ошибок Promise

  • Не нужен catch после каждого then
  • Один catch в конце перехватит все ошибки
  • Используй отдельный catch только для специальной обработки
  • Всегда пробрасывай (throw) или возвращай значение из catch
  • Предпочитай async/await вместо цепочек Promise
  • Логируй ошибки для отладки

Вывод

Нет, не нужен отдельный catch для каждого then. Один catch в конце обработает ошибки со всех предыдущих .then(). Используй отдельные catch только если нужна специальная обработка определенной ошибки. Для новых проектов переходи на async/await - это чище и понятнее.

Нужно ли делать отдельный catch для каждого then в Promise? | PrepBro