async/await и обработка ошибок

async/await — синтаксис, который превращает асинхронный код в линейный, и как ловить в нём ошибки.

async/await — синтаксический сахар над промисами: await приостанавливает функцию до выполнения промиса, не блокируя поток.

Основы

Функция с ключевым словом async всегда возвращает промис. Внутри неё await «разворачивает» промис: ждёт его значения и возвращает результат. Код читается сверху вниз, как синхронный, хотя выполняется асинхронно:

function delay(value, ms) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

async function main() {
  console.log("начало");
  const a = await delay("первый", 10);
  console.log(a);
  const b = await delay("второй", 10);
  console.log(b);
  console.log("конец");
}
main();

Вывод:

начало
первый
второй
конец

Обработка ошибок: обычный try/catch

Главное удобство await — ошибки ловятся привычным try/catch/finally, как в синхронном коде. Отклонённый промис превращается в исключение:

async function risky() {
  throw new Error("сбой в async");
}

async function main() {
  try {
    await risky();
  } catch (e) {
    console.log("поймали:", e.message);
  } finally {
    console.log("очистка выполнена");
  }
}
main();

Вывод:

поймали: сбой в async
очистка выполнена

Ошибки в цикле: изолируем каждую итерацию

Частая задача — обработать список, где отдельные элементы могут падать, но это не должно ронять весь процесс. Оборачиваем await в try/catch внутри цикла:

async function fetchUser(id) {
  if (id < 0) throw new Error("неверный id");
  return { id, name: "User" + id };
}

async function main() {
  const ids = [1, -2, 3];
  for (const id of ids) {
    try {
      const u = await fetchUser(id);
      console.log("получен:", u.name);
    } catch (e) {
      console.log("ошибка для id=" + id + ":", e.message);
    }
  }
}
main();

Вывод:

получен: User1
ошибка для id=-2: неверный id
получен: User3

Главная ловушка: последовательный await вместо параллельного

Если задачи независимы, не ждите их по очереди — запустите параллельно. Сравните: await a; await b; ждёт суммарно, а Promise.all([a, b]) — пока завершится самая долгая. В примере оба delay стартуют до await, поэтому идут параллельно:

function delay(value, ms) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

async function main() {
  const slow = delay("медленный (60мс)", 60);
  const fast = delay("быстрый (10мс)", 10);
  console.log("оба запущены, ждём...");
  const results = await Promise.all([slow, fast]);
  console.log("готово:", results.join(" | "));
}
main();

Вывод:

оба запущены, ждём...
готово: медленный (60мс) | быстрый (10мс)

Итог

  • async-функция всегда возвращает промис; await ждёт значение, не блокируя поток.
  • Ошибки ловятся обычным try/catch.
  • Независимые задачи запускайте через Promise.all, а не цепочкой await.
Проверьте себя
1. Что всегда возвращает функция, объявленная как async?
AЗначение из return напрямую
BПромис
Cundefined
DГенератор
2. Как поймать ошибку из отклонённого промиса при использовании await?
AЧерез .catch на самой функции
BОбычным try/catch вокруг await
CНикак — async глотает ошибки
DЧерез Promise.reject
3. Две независимые async-задачи нужно выполнить быстрее. Что выбрать?
Aawait a; await b; по очереди
BЗапустить обе и await Promise.all([a, b])
CВложить одну в другую
DИспользовать setTimeout
Поддержать проект