async/await

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

Зачем нужно

Цепочки .then при сложной логике становятся трудночитаемыми. async/await делает асинхронный код линейным, понятным и легко отлаживаемым.

Где используется

  • HTTP-запросы
  • Работа с базами данных
  • Чтение/запись файлов
  • Последовательные асинхронные операции
  • Любой код с Promise

Предпосылки

Promise, Error handling

Синтаксис async

// async перед функцией — она ВСЕГДА возвращает Promise
async function fetchData() {
  return 42; // Автоматически оборачивается в Promise.resolve(42)
}

fetchData.then(value => console.log(value)); // 42

// Разные формы
async function fn1() { /* ... */ }                  // Declaration
const fn2 = async function { /* ... */ };          // Expression
const fn3 = async () => { /* ... */ };               // Arrow
const obj = { async method { /* ... */ } };        // Method
class MyClass { async method { /* ... */ } }       // Class method

Синтаксис await

// await приостанавливает выполнение async-функции до разрешения Promise
async function loadUser(id) {
  // await можно использовать ТОЛЬКО внутри async-функции
  const response = await fetch(`/api/users/${id}`);
  const user = await response.json();
  return user;
}

// Эквивалент на Promise:
function loadUserPromise(id) {
  return fetch(`/api/users/${id}`)
    .then(response => response.json());
}

// await с не-Promise значением — просто возвращает его
async function example() {
  const value = await 42; // Оборачивается в Promise.resolve(42)
  console.log(value); // 42
}

Обработка ошибок: try/catch

async function loadData() {
  try {
    const response = await fetch('/api/data');

    if (!response.ok) {
      throw new Error(`HTTP ошибка: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Ошибка загрузки:', error.message);
    // Можно вернуть дефолтное значение
    return { items: , error: error.message };
  } finally {
    hideSpinner; // Выполнится всегда
  }
}

// Альтернатива: .catch на вызове
const data = await loadData.catch(err => {
  console.error(err);
  return null;
});

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

Последовательное (медленно, но нужно когда результат зависит от предыдущего)

async function sequential() {
  const user = await getUser(1);       // ~500ms
  const posts = await getPosts(user.id); // ~500ms (ждёт user)
  const comments = await getComments(posts[0].id); // ~500ms (ждёт posts)
  // Общее время: ~1500ms
  return { user, posts, comments };
}

Параллельное (быстро, когда запросы независимы)

async function parallel() {
  // Запускаем ВСЕ запросы одновременно
  const [users, posts, comments] = await Promise.all([
    fetch('/api/users').then(r => r.json()),    // ~500ms
    fetch('/api/posts').then(r => r.json()),    // ~500ms
    fetch('/api/comments').then(r => r.json())  // ~500ms
  ]);
  // Общее время: ~500ms (максимум из трёх)
  return { users, posts, comments };
}

// Или так:
async function parallelAlt() {
  // Запустить Promise ДО await
  const userPromise = getUser(1);
  const postsPromise = getPosts;

  // Теперь дождаться
  const user = await userPromise;
  const posts = await postsPromise;
}

Смешанный паттерн

async function mixed() {
  // Последовательно: нужен user для следующих запросов
  const user = await getUser(1);

  // Параллельно: posts и settings не зависят друг от друга
  const [posts, settings] = await Promise.all([
    getPosts(user.id),
    getSettings(user.id)
  ]);

  return { user, posts, settings };
}

Циклы с async/await

// Последовательная обработка
async function processSequential(urls) {
  const results = ;
  for (const url of urls) {
    const response = await fetch(url); // Ждёт каждый
    results.push(await response.json());
  }
  return results;
}

// Параллельная обработка
async function processParallel(urls) {
  const promises = urls.map(url => fetch(url).then(r => r.json()));
  return Promise.all(promises); // Все одновременно
}

// Параллельная с ограничением (пул)
async function processPool(urls, poolSize = 3) {
  const results = ;
  for (let i = 0; i < urls.length; i += poolSize) {
    const batch = urls.slice(i, i + poolSize);
    const batchResults = await Promise.all(
      batch.map(url => fetch(url).then(r => r.json()))
    );
    results.push(...batchResults);
  }
  return results;
}

// ВАЖНО: forEach НЕ работает с async/await
const urls = ['/api/1', '/api/2', '/api/3'];
// Неправильно:
urls.forEach(async (url) => {
  const data = await fetch(url); // Не дожидается!
});

// Правильно:
for (const url of urls) {
  const data = await fetch(url); // Дожидается каждого
}

Top-level await (ES2022)

// В ES-модулях можно использовать await без async-функции

// config.js (ES Module)
const response = await fetch('/api/config');
export const config = await response.json();

// main.js
import { config } from './config.js';
console.log(config); // Уже загружено

// Модуль не завершит загрузку, пока await не разрешится
// Другие модули, зависящие от него, будут ждать

Полезные паттерны

Retry с задержкой

async function fetchWithRetry(url, retries = 3, delay = 1000) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      console.warn(`Попытка ${i + 1} не удалась, повтор через ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
      delay *= 2; // Экспоненциальный backoff
    }
  }
}

Таймаут для async-операции

async function withTimeout(asyncFn, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout( => reject(new Error('Timeout')), ms)
  );
  return Promise.race([asyncFn, timeout]);
}

// Использование
const data = await withTimeout(
   => fetch('/api/slow-endpoint').then(r => r.json()),
  5000
);

IIFE для async

// Когда нужен await в не-async контексте
(async () => {
  const data = await fetchData;
  console.log(data);
});

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

1. Забытый await

async function bad() {
  const response = fetch('/api'); // Нет await!
  const data = response.json();  // TypeError: response.json() is not a function
  // response — это Promise, не Response!
}

2. Последовательные await вместо параллельных

// Медленно: 3 секунды
const a = await delay1s;
const b = await delay1s;
const c = await delay1s;

// Быстро: 1 секунда
const [a, b, c] = await Promise.all([delay1s, delay1s, delay1s]);

3. await в неасинхронной функции

// function bad() {
//   const data = await fetch('/api'); // SyntaxError!
// }

// Решение: пометь функцию как async
async function good() {
  const data = await fetch('/api');
}

4. Необработанные ошибки в async

// Незаполненный catch
async function risky() {
  const data = await fetch('/broken');
  return data.json;
}
risky; // UnhandledPromiseRejection!

// Решение: всегда обрабатывай
risky.catch(console.error);
// Или вызывай из try/catch

Практика

  1. Перепиши Promise-цепочку из 4 шагов в async/await
  2. Загрузи 5 URL параллельно с Promise.all и async/await
  3. Реализуй функцию retry с экспоненциальным backoff
  4. Напиши async-функцию, обрабатывающую массив последовательно и параллельно

Связанные темы

Ресурсы


🎓 Источник: Asynchronous functions, async/await, thenable, error handling

  • 📅 2018-12-11 · YouTube
  • Тезисы:
    • async можно ставить перед любой формой функции, КРОМЕ конструктора класса (синтаксическая ошибка).
    • async всегда возвращает Promise — даже если return примитив или вообще ничего нет.
    • await распаковывает Promise и thenable; над скаляром await работает синхронно (оборачивает в Promise.resolve).
    • await ищет у операнда метод then — поэтому работает с любым Thenable.
    • Между try/catch (синхронный) и promise.catch приоритет у promise.catch (если он стоит сразу после await).
    • Цикл for await...of работает с [Symbol.asyncIterator]; перед каждой итерацией стек пустеет.
  • Цитата:

    «await делает код плоским — решает основную проблему вложенности промисов и callback hell в одной конструкции.»


🎓 Источник: JavaScript собеседование — вопросы по асинхронному программированию

  • 📅 2024-06-29 · YouTube
  • Тезисы:
    • await с не-thenable значением работает синхронно — V8 оптимизирует: не создаёт лишнюю микрозадачу.
    • Разделяй бизнес-логику и async-абстракции (тесты на абстракции в разы важнее тестов на бизнес-логику).
    • Concurrency — самое широкое понятие; за разные ресурсы (connection, файл, CPU) конкуренция организуется по-разному.
    • I/O — внешние операции в async-обёртке; реально параллелить можно только тредами / процессами / worker_threads.
    • Полифилы промисов в 2024 уже не нужны — нативно работает быстрее.
  • Цитата:

    «Внутри одного треда — async, между тредами — parallel. Не путать эти уровни.»


⚡ Источник: Производительность Async Function · AsForJS

  • 📅 2025-07-08 · YouTube
  • Тезисы:
    • Async-функция внутри — это генератор с Suspend/Resume. В байт-коде V8 это видно явно.
    • Async-код почти не оптимизируется V8 на уровне всей функции — оптимизатор работает в границах функции, а граница ломается на каждом await.
    • Каждый await "замораживает" функцию — выделяется память на сохранение контекста (закрытие).
    • Байт-кода в async-функции почти вдвое больше, чем в синхронной.
    • Последовательные await запускают сетевые запросы один за другим — типичная утечка времени. Запускать параллельно: Promise.all([fetch(a), fetch(b)]).
    • await rejectedPromise сам бросает исключение; try/catch без await НЕ ловит rejected Promise.
    • Lишний await перед return стоит одной микрозадачи (исправлено ZeroCostAsync ~Node 12) — современно не критично.
  • Цитата:

    «Async — это генератор с маленькой добавкой. Не пиши последовательный async-код там, где не надо: это экономия на спичках, а тратится дорогая память на continuation.»

  • Цитата:

    «try/catch без await НЕ ловит промисы — это типичный неправильный ответ с собеседования. await сам делает throw на rejected.»


🎓 Источник: Asynchronous Programming in Node.js and JavaScript

  • 📅 2018-09-19 · YouTube
  • Тезисы:
    • async/await визуально превращает асинхронный код в синхронный, но под капотом — те же коллбэки.
    • У промисов нет встроенного таймаута и отмены — нужны обёртки (Promise.race + timeout, AbortController).
    • Адаптеры toAsync, promisify, callbackify позволяют смешивать стили без потери читаемости.
    • Композиция асинхронных функций через массивы (flow, pipeAsync) — основа функциональной асинхронности.
    • Лучше явно отказать, чем зависнуть — таймауты и quotas критичны в production-async-коде.