async/await

async/await

Технический фундамент async-функций

async-функция всегда возвращает Promise, даже если внутри возвращается обычное значение. Это означает:

  • return value внутри async эквивалентен Promise.resolve(value);
  • throw error внутри async эквивалентен Promise.reject(error);
  • await только "раскрывает" Promise в локальной точке функции.

Понимание этого соответствия упрощает переход между then/catch и async/await без магии.

Почему async/await так любят в прод-коде

Promise-цепочки уже решают проблему callback hell, но при длинных сценариях они все равно читаются тяжелее, чем линейный код. async/await дает синтаксис, который выглядит почти как синхронный, но работает асинхронно.

Ключевой момент: async/await это синтаксический слой над Promise, а не отдельный механизм выполнения.

Проверь себя: почему функция с async всегда возвращает Promise?

Базовый синтаксис

  • async ставится перед функцией.
  • await можно использовать только внутри async-функции.
  • await "ждет" завершения Promise и возвращает его результат.
function loadUser() {
  return Promise.resolve({ id: 1, name: 'Саша' });
}

async function showUser() {
  const user = await loadUser();
  console.log(user.name);
}

showUser();

Смотри, что важно: await не блокирует весь JavaScript-движок, он приостанавливает только текущую async-функцию до готовности результата.

Обработка ошибок с try...catch

В async/await ошибки удобно ловить привычным try...catch.

async function loadProfile() {
  try {
    const response = await fetch('/api/profile');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Не удалось загрузить профиль:', error.message);
    return null;
  }
}

Это читается как линейный сценарий: запрос -> парсинг -> возврат результата.

Здесь часто путаются: если забыть await response.json(), ты получишь Promise, а не готовый объект данных.

Проверь себя: какой тип значения будет у data без await?

Последовательное и параллельное ожидание

Иногда нужно дождаться операций по очереди, иногда выгоднее запускать их параллельно.

Последовательно:

const user = await loadUser();
const orders = await loadOrders(user.id);

Параллельно:

const [user, settings] = await Promise.all([loadUser(), loadSettings()]);

Ключевой момент: если операции независимы, параллельный запуск обычно быстрее.

Микро-сценарии из реального интерфейса

  1. Экран профиля.
  • await для загрузки данных пользователя.
  • если ошибка - показываем fallback-состояние.
  1. Дашборд.
  • параллельно подгружаем карточки метрик через Promise.all.
  • после получения всех данных рендерим экран целиком.

Это дает более предсказуемый UX, чем случайный порядок отрисовки фрагментов.

Типичные ошибки новичков

  • Использовать await вне async-функции.
  • Забывать try...catch вокруг потенциально падающих операций.
  • Писать лишнюю последовательность там, где можно параллельно.
  • Забывать вернуть результат из async-функции.
  • Использовать await внутри forEach и ожидать, что цикл "подождет".
// Плохо: forEach не умеет "ждать" async-callback
items.forEach(async (item) => {
  await saveItem(item);
});

// Последовательно (если нужно строго по очереди)
for (const item of items) {
  await saveItem(item);
}

// Параллельно (если операции независимы)
await Promise.all(items.map((item) => saveItem(item)));
async function getValue() {
  await Promise.resolve(10);
}

getValue().then((v) => console.log(v)); // undefined

Анти-провал: если функция должна отдавать данные, возвращай их явно после await.

Что будет, если изменить входные данные

Если fetch('/api/profile') вернет ошибку сети, управление перейдет в catch, и функция вернет null. Если ответ корректный, вернется объект профиля. Такое поведение легко предсказать и протестировать.

Проверь себя: зачем в catch возвращать осмысленный fallback, а не просто молча глотать ошибку?

Дополнительный пример: эквивалент return и throw в async.

async function getNumber() {
  return 42;
}

async function failTask() {
  throw new Error('Task failed');
}

Краткий итог

  • async/await делает асинхронный код ближе к линейному стилю.
  • await работает только внутри async и ожидает Promise.
  • Ошибки в async-коде удобно обрабатывать через try...catch.
  • Независимые операции лучше запускать параллельно через Promise.all.
  • Явные возвраты и понятные fallback делают async-логику устойчивой.