Ошибки в async/await

Урок: Ошибки в async/await

Введение

Когда мы начинаем использовать async / await, код становится намного чище и понятнее. Он выглядит почти как обычный синхронный: сверху вниз, шаг за шагом.

Но вместе с этим появляется иллюзия, что всё работает «как обычно». И именно здесь кроется проблема.

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

В async / await ошибки тоже не исчезают — они просто меняют форму. И если не понимать, как они работают, код может вести себя неожиданно:

  • ошибки не ловятся;
  • программа «падает»;
  • данные оказываются не теми, что ожидались.

В этом уроке разберём, как именно работают ошибки в async / await и как с ними правильно обращаться.


Как возникают ошибки в async/await

Рассмотрим простой пример:

async function main() {
  await Promise.reject('Ошибка!');
}

main().catch((e) => console.log('необработанная ошибка async:', e));

Здесь:

  • Promise.reject создаёт ошибку;
  • await «пробрасывает» её наружу.

Результат: ошибка появится в консоли, и программа завершится с ошибкой.

Важно понять: await делает так, что ошибка ведёт себя как обычная синхронная.


Обработка ошибок через try/catch

Чтобы перехватить ошибку, используется try / catch.

async function main() {
  try {
    await Promise.reject('Ошибка!');
  } catch (error) {
    console.log('Поймали ошибку:', error);
  }
}

main();

Результат:

Поймали ошибку: Ошибка!

Разберём:

  • await превращает ошибку Promise в «обычную»;
  • try / catch её перехватывает;
  • программа продолжает работу.

Это основной способ обработки ошибок в async / await.


Ошибка без try/catch

Если не использовать try / catch, ошибка не будет обработана:

async function main() {
  let result = await Promise.reject('Ошибка!');
  console.log(result);
}

main().catch((e) => console.log('await пробросил ошибку, строка ниже не выполнилась:', e));

В этом случае:

  • выполнение остановится;
  • строка console.log не выполнится;
  • ошибка попадёт в консоль.

Это важно: await не «гасит» ошибку, он только передаёт её дальше.


Ошибка в нескольких await

Когда операций несколько, важно понимать, где именно может произойти ошибка.

async function main() {
  try {
    let a = await Promise.resolve(1);
    let b = await Promise.reject('Ошибка!');
    let c = await Promise.resolve(3);

    console.log(a, b, c);
  } catch (error) {
    console.log('Ошибка:', error);
  }
}

main();

Результат:

Ошибка: Ошибка!

Что произошло:

  • a успешно получил значение;
  • на b произошла ошибка;
  • выполнение остановилось;
  • c уже не выполняется.

Это важное поведение: ошибка прерывает дальнейшее выполнение внутри try.


Ошибка вне await

Иногда ошибка возникает не в Promise, а в обычном коде:

async function main() {
  try {
    let user = null;
    console.log(user.name);
  } catch (error) {
    console.log('Ошибка:', error.message);
  }
}

main();

Результат:

Ошибка: Cannot read properties of null

try / catch работает и с синхронными, и с асинхронными ошибками внутри async функции.


Ошибка без await — не ловится

Одна из самых частых ошибок — забыть await.

async function main() {
  try {
    Promise.reject('Ошибка!').catch((e) => {
      console.log(
        'без await try/catch не видит reject — сообщение только из .catch Promise:',
        e,
      );
    });
  } catch (error) {
    console.log('Поймали:', error);
  }
}

main();

Без await ветка catch у try не выполняется; сообщение в консоли появляется только из .catch, навешанного на сам Promise (в «голом» примере без этого ещё и возник бы необработанный rejection).

Почему:

  • ошибка происходит внутри Promise;
  • без await она не «выбрасывается» наружу;
  • try / catch её не видит.

Исправление:

(async () => {
  try {
    await Promise.reject('Ошибка!');
  } catch (e) {
    console.log('с await отказ ловится в try/catch:', e);
  }
})();

Теперь ошибка будет поймана.


Ошибка в then внутри async

Иногда смешивают then и await:

async function main() {
  console.log(
    'Антипаттерн из урока: Promise.reject().then(...) в try без await — try/catch не перехватывает отказ Promise.',
  );
  await Promise.resolve();
}

main();

Ошибка снова не ловится.

Почему:

  • then работает отдельно;
  • try / catch не контролирует эту цепочку.

Правильный подход — использовать либо await, либо catch.


Глобальная ошибка async функции

Если ошибка не обработана, она «выходит наружу».

async function main() {
  throw new Error('Ошибка!');
}

main().catch((e) => console.log('необработанный throw из async:', e.message));

В браузере это приведёт к необработанной ошибке (unhandled promise rejection).

Это может:

  • ломать приложение;
  • создавать нестабильное поведение.

Поэтому важно всегда обрабатывать ошибки.


Практическое применение

Ошибки в async / await особенно важны при работе с:

  • fetch (запросы к серверу);
  • API;
  • базами данных;
  • пользовательским вводом.

Например:

async function loadData() {
  try {
    throw new Error('имитация сбоя сети или JSON');
    let response = await fetch('https://api.com/data');
    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.log('Ошибка загрузки данных');
  }
}

loadData();

Здесь:

  • ошибка может быть в запросе;
  • или в обработке данных;
  • try / catch защищает приложение.

Итоговое понимание

Ошибки в async / await — это те же ошибки, что и раньше, но с особенностями асинхронности.

Ключевые моменты:

  • await «превращает» ошибку Promise в обычную;
  • try / catch ловит только ошибки внутри await;
  • без await ошибка может остаться незамеченной;
  • ошибка останавливает выполнение функции.

Главная идея в том, что async / await делает код проще, но требует внимательности. Ошибки никуда не исчезают — их просто нужно правильно перехватывать и обрабатывать.