Когда в 2017-м async/await добрались до production-сборок, это казалось синтаксическим сахаром над промисами. Но за пять лет паттерны эволюционировали — теперь это полноценный инструмент для работы с асинхронными потоками. Разберу три сценария, где современные подходы реально сокращают когнитивную нагрузку.
Top-level await: когда IIFE надоел
Раньше для использования await приходилось оборачивать код в Immediately Invoked Function Expression:
(async () => {
const config = await fetchConfig();
initApp(config);
})();
С ES2022 и модулями (.mjs или type=“module”) можно писать await на верхнем уровне:
// config.mjs
export const config = await fetch('/config.json').then(r => r.json());
// app.mjs
import { config } from './config.mjs';
console.log(config.apiUrl); // Уже разрешённый объект
Где ловушка: модуль с top-level await блокирует загрузку зависимостей. Если config.mjs тормозит, все импортирующие его модули ждут. В React/Vue такое лучше не использовать для динамических данных — только для статической конфигурации.
For-await-of: стримы без колбэков
Представьте, что вы читаете ленту Twitter через API с пагинацией. Классический подход с while и await:
let cursor = null;
let allTweets = [];
do {
const res = await fetch(`/api/tweets?cursor=${cursor}`);
const { tweets, nextCursor } = await res.json();
allTweets.push(...tweets);
cursor = nextCursor;
} while (cursor);
С async generators и for-await-of код становится линейным:
async function* fetchTweets() {
let cursor = null;
do {
const res = await fetch(`/api/tweets?cursor=${cursor}`);
const { tweets, nextCursor } = await res.json();
yield tweets;
cursor = nextCursor;
} while (cursor);
}
// Использование
for await (const tweets of fetchTweets()) {
renderBatch(tweets);
}
Что под капотом: for-await-of работает с любым объектом, у которого есть Symbol.asyncIterator. Поддерживается в Node.js стримах, WebSocket, EventSource.
Promise.allSettled: graceful degradation по-современному
Допустим, у вас дашборд с виджетами. Если один API упал, остальные должны продолжать работать. До 2020-го писали костыли с .catch:
const [users, posts] = await Promise.all([
fetchUsers().catch(() => []),
fetchPosts().catch(() => [])
]);
Теперь можно явно обрабатывать статусы:
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchAnalytics()
]);
const data = {
users: results[0].status === 'fulfilled' ? results[0].value : [],
posts: results[1].status === 'fulfilled' ? results[1].value : [],
analytics: results[2].status === 'fulfilled' ? results[2].value : null
};
Производительность: allSettled не быстрее all — просто другая семантика. Не используйте для последовательных запросов, только для параллельных независимых операций.
Комбинированный пример: дашборд с live-данными
Соединим все три подхода в реальном сценарии:
// config.mjs
export const config = await fetch('/config').then(r => r.json());
// api.mjs
export async function* streamMetrics() {
const es = new EventSource('/live');
for await (const event of es) {
yield JSON.parse(event.data);
}
}
// dashboard.mjs
import { config } from './config.mjs';
import { streamMetrics } from './api.mjs';
const [userData, postsData] = await Promise.allSettled([
fetch(`/users/${config.userId}`),
fetch(`/posts/${config.userId}`)
]);
renderStatic({
user: userData.status === 'fulfilled' ? await userData.value.json() : null,
posts: postsData.status === 'fulfilled' ? await postsData.value.json() : []
});
for await (const metric of streamMetrics()) {
updateChart(metric);
}
Что это даёт:
- Конфиг загружается до инициализации приложения
- Статические данные обрабатываются с учётом возможных ошибок
- Live-данные приходят без вложенных колбэков
Когда не использовать
Async/await — не серебряная пуля. Вот где он добавляет сложности:
- Циклы с side-эффектами — легко случайно сделать последовательные запросы вместо параллельных
- Обработка прерываний —
AbortControllerсложно интегрировать с for-await-of - Глубокая вложенность — иногда
.thenцепочка читается проще
Попробуйте переписать один из своих модулей с этими паттернами. Особенно хорошо они работают в связке с TypeScript — подсказки типов помогают не запутаться в статусах Promise.allSettled.
Попробуй сам: Cursor — AI-редактор для разработчиков.