Перейти к содержанию
Innovialabs

Промисы и async/await в JavaScript: практическое руководство

Практика3 мин

Коротко

Промис — это объект с будущим результатом, а async/await — удобный синтаксис над ним; независимые запросы запускайте параллельно через Promise.all, а ошибки ловите через try/catch.

Коротко о главном

Промис — объект, который представляет результат асинхронной операции: он либо выполнится (fulfilled), либо отклонится с ошибкой (rejected). async/await — синтаксис поверх промисов, который позволяет писать асинхронный код так, будто он синхронный.

Три правила, которые закрывают большинство задач:

  • ошибки ловите через try/catch вокруг await;
  • независимые операции запускайте параллельно через Promise.all;
  • не забывайте await — иначе получите промис вместо данных.

От колбэков к промисам

Раньше асинхронность строилась на колбэках, и вложенность быстро росла:

getUser(id, (err, user) => {
  if (err) return handle(err);
  getOrders(user.id, (err, orders) => {
    if (err) return handle(err);
    render(orders);
  });
});

С промисами это превращается в плоскую цепочку с одной точкой обработки ошибок:

getUser(id)
  .then((user) => getOrders(user.id))
  .then((orders) => render(orders))
  .catch(handle);

async/await: тот же код, только читаемее

async function showOrders(id) {
  try {
    const user = await getUser(id);
    const orders = await getOrders(user.id);
    render(orders);
  } catch (err) {
    handle(err);
  } finally {
    hideLoader();
  }
}

Важно помнить: async-функция всегда возвращает промис. Даже return 5 внутри неё вернёт Promise<5>.

Если ответ сервера с ошибкой (например, статус 404), fetch не отклоняет промис — проверяйте response.ok сами:

const res = await fetch('/api/items');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const items = await res.json();

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

Частая ошибка — ждать независимые запросы по очереди:

// последовательно: второй запрос стартует только после первого
const user = await getUser(id);
const news = await getNews();

Если запросы не зависят друг от друга, запустите их одновременно:

const [user, news] = await Promise.all([getUser(id), getNews()]);

Последовательность нужна, только когда следующий шаг использует результат предыдущего.

Promise.all, allSettled, race, any

МетодКогда завершаетсяКогда использовать
Promise.allвсе успешны или первая ошибкавсе результаты обязательны
Promise.allSettledкогда все завершилисьнужны и успехи, и ошибки
Promise.raceпервый завершившийся, неважно кактаймауты
Promise.anyпервый успешныйнесколько зеркал одного ресурса

Пример таймаута через race:

const timeout = (ms) =>
  new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms));

const data = await Promise.race([fetchData(), timeout(5000)]);

Для отмены самого fetch используйте AbortController — race лишь перестаёт ждать, но запрос продолжает выполняться.

Частые ошибки

  • await внутри forEach. forEach не ждёт промисы. Используйте for...of для последовательной обработки или Promise.all с map для параллельной.
  • Забытый await. В переменной окажется промис, а ошибка уйдёт в необработанные.
  • Пустой catch. Проглоченная ошибка — худший вид бага. Хотя бы логируйте её.
  • Слишком много параллельных запросов. Promise.all по тысяче элементов может перегрузить API. Обрабатывайте пачками.
  • Смешивание стилей. В одной функции лучше выбрать либо then, либо await.
// неправильно
items.forEach(async (item) => await save(item));

// параллельно
await Promise.all(items.map((item) => save(item)));

// последовательно
for (const item of items) {
  await save(item);
}

Частые вопросы

Работают они одинаково, async/await просто читается легче, особенно с условиями и циклами. then удобен для коротких цепочек и там, где нельзя использовать await.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.