async/await
async/await — синтаксический сахар над Promise, позволяющий писать асинхронный код в синхронном стиле.
Зачем нужно
Цепочки .then при сложной логике становятся трудночитаемыми. async/await делает асинхронный код линейным, понятным и легко отлаживаемым.
Где используется
- HTTP-запросы
- Работа с базами данных
- Чтение/запись файлов
- Последовательные асинхронные операции
- Любой код с Promise
Предпосылки
Синтаксис 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
Практика
- Перепиши Promise-цепочку из 4 шагов в async/await
- Загрузи 5 URL параллельно с
Promise.allи async/await - Реализуй функцию
retryс экспоненциальным backoff - Напиши 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-коде.