Асинхронность: async/await и event loop
async/await
async перед функцией делает её асинхронной. Внутри можно использовать await:
async function load() {
const res = await fetch('/api/data');
const data = await res.json();
return data;
}
await приостанавливает функцию, пока промис не разрешится.
Асинхронная функция возвращает промис
async function getValue() {
return 42;
}
getValue().then((v) => console.log(v)); // 42
Обработка ошибок
async function load() {
try {
const data = await fetchData();
return data;
} catch (error) {
console.error('Ошибка:', error);
throw error;
}
}
try/catch работает с await, как с синхронным кодом.
Последовательность
async function process() {
const user = await getUser();
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
return comments;
}
Каждый шаг ждёт предыдущий. Это может быть медленно, если запросы независимы.
Параллельность
async function process() {
const [user, posts, comments] = await Promise.all([
getUser(),
getPosts(),
getComments(),
]);
return { user, posts, comments };
}
Все три запроса идут одновременно.
Event loop
JavaScript однопоточный, но асинхронный. Event loop — механизм, который позволяет выполнять асинхронный код.
Очереди
- Call stack — стек вызовов
- Microtasks — промисы,
queueMicrotask,MutationObserver - Macrotasks —
setTimeout,setInterval, I/O, UI-события
Порядок выполнения
- Синхронный код выполняется полностью
- Затем — все microtasks
- Затем — одна macrotask
- Снова все microtasks
- Следующая macrotask
- И так далее
Пример
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 1, 4, 3, 2
- Синхронный код:
1,4 - Microtasks:
3 - Macrotasks:
2
Микрозадачи важнее
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
// promise
// timeout
Промисы всегда раньше таймеров.
Асинхронный код не блокирует
console.log('начало');
setTimeout(() => console.log('середина'), 0);
console.log('конец');
// начало, конец, середина
setTimeout не блокирует выполнение.
Топ-левел await
В модулях:
const data = await fetch('/api/data').then((r) => r.json());
Работает только в ES-модулях и современных окружениях.
Полезные приёмы
Задержка
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await sleep(1000);
Таймаут для промиса
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
),
]);
}
Последовательная обработка
for (const item of items) {
await process(item); // по одному
}
Параллельная
await Promise.all(items.map((item) => process(item)));
С ограничением:
async function processWithLimit(items, limit) {
const results = [];
const executing = [];
for (const item of items) {
const p = process(item).then((r) => {
executing.splice(executing.indexOf(p), 1);
return r;
});
results.push(p);
executing.push(p);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
Итоги
async/await— синтаксис поверх промисовtry/catchдля ошибокPromise.allдля параллельности- Event loop: stack → microtasks → macrotasks
- Промисы раньше таймеров
- Ограничивай параллельность при нагрузке