JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /try/catch для обработки ошибок в async/await

try/catch для обработки ошибок в async/await

Модуль 4: Node.js, Next.js и Angular
2 уровень , 6 лекция
Открыта

1. Почему обработка ошибок в асинхронном коде — это важно

В синхронном коде всё просто: если функция выбрасывает ошибку (throw), выполнение кода прерывается и ошибка "всплывает" вверх по стеку вызовов, пока не встретит блок try/catch. Но с асинхронным кодом всё не так очевидно.

Когда вы работаете с промисами, ошибки ловятся методом .catch(). Но как быть, если вы используете синтаксис async/await? Можно ли просто обернуть вызов асинхронной функции в try/catch? Как ловить ошибки, если их несколько? А если ошибка произошла в середине цепочки?

Давайте разбираться!

try/catch с async/await: базовый синтаксис

Вот как выглядит обработка ошибок в асинхронной функции с помощью try/catch:

async function fetchUser() {
  try {
    const response = await fetch('https://api.github.com/users/octocat');
    if (!response.ok) {
      throw new Error('Сервер вернул ошибку: ' + response.status);
    }
    const user = await response.json();
    console.log('Пользователь:', user.login);
  } catch (error) {
    console.error('Произошла ошибка при загрузке пользователя:', error.message);
  }
}

Как это работает:

  • Всё, что находится внутри блока try, выполняется "по очереди": сначала fetch, потом response.json().
  • Если где-то в этом процессе возникает ошибка (например, сеть недоступна, или сервер вернул ошибку, или что-то пошло не так при разборе JSON), выполнение сразу "перепрыгивает" в блок catch.
  • В блоке catch вы получаете объект ошибки (error), с которым можно сделать что угодно: вывести на экран, залогировать, повторить попытку и т.д.

2. Пример: асинхронная функция с ошибкой

Давайте рассмотрим классический пример — загрузка данных с сервера, который иногда бывает недоступен.

async function getJoke() {
  try {
    const response = await fetch('https://api.chucknorris.io/jokes/random');
    // Проверяем, всё ли ок
    if (!response.ok) {
      throw new Error('Не удалось получить шутку: ' + response.status);
    }
    const data = await response.json();
    console.log(data.value);
  } catch (err) {
    console.log('Ошибка при получении шутки:', err.message);
  }
}

getJoke();

Что произойдёт, если сервер недоступен или вернёт ошибку?

  • Если сервер не отвечает, то fetch выбросит ошибку, и выполнение сразу попадёт в catch.
  • Если сервер отвечает, но возвращает, например, 404, мы сами выбрасываем ошибку через throw new Error(...).
  • Если всё хорошо, выводим шутку.

3. Возвращаемое значение из async-функции и try/catch

Что возвращает async-функция? Всегда — промис. Если внутри функции произошла ошибка и она не была обработана внутри, то промис будет отклонён (rejected) с этой ошибкой.

Пример:

async function brokenFunction() {
  throw new Error('Что-то сломалось!');
}

brokenFunction()
  .then(() => console.log('Всё хорошо!'))
  .catch(err => console.log('Ошибка:', err.message));

Вывод:

Ошибка: Что-то сломалось!

Если ошибка была обработана внутри функции (например, с помощью try/catch), промис будет успешно выполнен (resolved), и ошибка наружу не выйдет.

4. try/catch вокруг await: ловим только ошибки из await

Иногда нужно обработать только ошибку от одной конкретной асинхронной операции. Для этого можно обернуть в try/catch только этот участок кода.

async function partialTryCatch() {
  let user;
  try {
    const response = await fetch('https://api.github.com/users/octocat');
    user = await response.json();
  } catch (err) {
    console.log('Ошибка при загрузке пользователя:', err.message);
    user = { login: 'Гость' };
  }
  console.log('Имя пользователя:', user.login);
}

partialTryCatch();

Зачем так делать?
Если у вас несколько независимых асинхронных операций, и вы хотите для каждой из них реализовать свою логику обработки ошибок, не обязательно оборачивать всю функцию в один большой try/catch.

5. try/catch в "верхнем уровне" (top-level await)

В современных версиях Node.js и в модулях ES можно использовать await вне функций — на верхнем уровне файла. Ошибки в таком случае тоже можно ловить через try/catch:

try {
  const response = await fetch('https://api.github.com/users/octocat');
  const user = await response.json();
  console.log(user.login);
} catch (err) {
  console.error('Ошибка на верхнем уровне:', err.message);
}

Важно:
В обычных скриптах (не модулях) такой код не сработает — await вне функции использовать нельзя. Но в модулях (type="module") или в Node.js с поддержкой ESM — можно.

6. Пример из жизни: обработка ошибок в цепочке асинхронных действий

Давайте представим, что у нас есть простое приложение: пользователь вводит имя города, а мы показываем ему погоду. Для этого нужно:

  1. Отправить запрос на сервер погоды.
  2. Обработать ответ.
  3. Если что-то пошло не так — вывести пользователю сообщение.
async function getWeather(city) {
  try {
    const response = await fetch(`https://wttr.in/${city}?format=j1`);
    if (!response.ok) {
      throw new Error('Не удалось получить погоду');
    }
    const data = await response.json();
    console.log(`Погода в городе ${city}: ${data.current_condition[0].temp_C}°C`);
  } catch (err) {
    console.log(`Ошибка при получении погоды для города "${city}":`, err.message);
  }
}

getWeather('Moscow');

7. Генерация собственных ошибок внутри async-функций

Иногда нужно не только ловить ошибки, но и самому их выбрасывать, если, например, пришли некорректные данные или не выполнено какое-то условие.

async function divide(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('Оба аргумента должны быть числами!');
  }
  if (b === 0) {
    throw new Error('Деление на ноль запрещено!');
  }
  return a / b;
}

async function main() {
  try {
    let result = await divide(10, 0);
    console.log('Результат:', result);
  } catch (err) {
    console.error('Ошибка:', err.message);
  }
}

main();

Обратите внимание:

  • Если ошибка выброшена через throw, она тут же попадает в ближайший catch.
  • Можно выбрасывать как стандартные ошибки (Error, TypeError), так и свои.

8. try/catch и параллельные await

Если вы делаете несколько асинхронных операций параллельно с помощью Promise.all, ошибка в любом из промисов приведёт к отклонению всего промиса. Ловить ошибку нужно через try/catch вокруг await Promise.all(...):

async function loadAll() {
  try {
    const [user, posts] = await Promise.all([
      fetch('https://api.github.com/users/octocat').then(r => r.json()),
      fetch('https://jsonplaceholder.typicode.com/posts/1').then(r => r.json())
    ]);
    console.log('User:', user.login);
    console.log('Post:', posts.title);
  } catch (err) {
    console.error('Ошибка при загрузке данных:', err.message);
  }
}

loadAll();

9. Типичные ошибки при работе с try/catch и async/await

Ошибка №1: забыли await внутри try/catch

Очень частая ошибка — написать так:

async function oops() {
  try {
    fetch('https://api.github.com/users/octocat');
    // ошибка не поймается catch-ем, если промис завершится с ошибкой!
  } catch (err) {
    console.error('Ошибка:', err.message);
  }
}

Здесь fetch возвращает промис, но мы не используем await, поэтому ошибка внутри промиса не будет поймана этим catch. Всегда используйте await для асинхронных операций внутри try/catch!

Ошибка №2: try/catch не ловит синтаксические ошибки вне async-функции

try {
  async function foo() {
    await fetch('broken-url');
  }
  foo();
} catch (err) {
  // Этот catch не поймает ошибку из async-функции!
}

Здесь ошибка произойдёт внутри промиса, и внешний catch её не увидит. Ошибку нужно ловить либо внутри async-функции через try/catch, либо через .catch() у промиса:

foo().catch(err => { /* обработка */ });

Ошибка №3: забыли обработать ошибку вообще

Если не обработать ошибку из async-функции, она "всплывёт" и может привести к некрасивому сообщению в консоли или даже завершению работы Node.js-приложения. Всегда оборачивайте await в try/catch или добавляйте .catch() у промиса!

Ошибка №4: ловим слишком много или слишком мало

Иногда новички оборачивают в try/catch слишком большой участок кода, и не могут понять, где именно возникла ошибка. Лучше ловить только то, что действительно может "упасть", и делать обработку ошибок максимально локальной.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 2 уровень, 6 лекция
Недоступна
Локальная обработка ошибки при делении
Локальная обработка ошибки при делении
1
Задача
Модуль 4: Node.js, Next.js и Angular, 2 уровень, 6 лекция
Недоступна
Ошибка в цепочке await внутри try/catch
Ошибка в цепочке await внутри try/catch
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ