Ошибки async-функции: где действительно работает try/catch

2 мин чтения

Где поставить await, как обработать отказ Fetch и вернуть кнопку в рабочее состояние после ошибки.

Ожидание и обработка результата
  1. awaitДожидаемся Promise внутри try
  2. Проверка ответаHTTP-ошибку распознаём через response.ok
  3. catch / finallyПоказываем ошибку и возвращаем кнопку в рабочее состояние

Вокруг вызова стоит try/catch, но ошибка async-функции всё равно уходит мимо него. Обычно дело в том, что код не дождался Promise. Разберём обработчик кнопки, который ждёт ответ, проверяет HTTP-статус и данные, а затем возвращает интерфейс в рабочее состояние независимо от результата.

Один обработчик со всеми исходами

const button = document.querySelector('#refresh');
const status = document.querySelector('#status');
async function readCounter() {
  const response = await fetch('/api/counter');
  if (!response.ok) throw new Error('HTTP ' + response.status);
  const value = await response.json();
  if (!Number.isInteger(value.count) || value.count < 0) {
    throw new Error('Неверный счётчик');
  }
  return value.count;
}
button.addEventListener('click', async function () {
  button.disabled = true;
  try {
    status.textContent = String(await readCounter());
  } catch (error) {
    status.textContent = 'Не удалось обновить счётчик';
  } finally {
    button.disabled = false;
  }
});

Пример объединяет несколько возможных отказов: сетевую ошибку, неуспешный HTTP-статус, повреждённый JSON и неверное значение поля. Fetch сам по себе не отклоняет Promise только из-за ответа 404, поэтому статус проверяется явно. Блок finally восстанавливает кнопку после успеха и ошибки. Текст для пользователя отделён от технических деталей; если требуется журналирование, сведения об ошибке передают в предусмотренный проектом канал без содержимого приватного ответа.

Не теряем незавершённую работу

Если убрать await перед readCounter, управление может выйти из try раньше, чем возникнет отказ. Аналогичная проблема появляется у асинхронного callback внутри forEach: внешний цикл не ожидает возвращаемые Promise. Для последовательной обработки удобен цикл с await внутри; для параллельной нужно явно собрать операции и определить политику ошибок. Выбор влияет на порядок выполнения и нагрузку, поэтому его нельзя считать чисто синтаксической заменой.

Отключение кнопки предотвращает повторное нажатие только в этом интерфейсе. Оно не гарантирует единственность серверной операции и не управляет запросами из других вкладок. В примере выполняется чтение; для изменения данных понадобятся дополнительные договорённости. Убедитесь также, что элементы существуют к моменту подключения обработчика. Функция, которая сама перехватила ошибку и вернула обычное значение, считается успешно завершившейся: вызывающая сторона больше не увидит отказ автоматически.

Сеть, HTTP-ошибка и неверные данные

Проверьте нормальное число, отрицательное значение, HTML вместо JSON, ответ 500 и отключённую сеть. При отказе сообщение должно появиться, а кнопка — снова включиться. Посмотрите консоль на необработанные Promise. Отдельно проверьте ноль: проверка на «пустое» не должна отклонять допустимое значение.

Источники и дальнейшее чтение