Как выполняется асинхронный код?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как выполняется асинхронный код в JavaScript
JavaScript работает по принципу event loop — это механизм, который координирует выполнение синхронного кода, асинхронных операций и обработку событий. Это критично понимать для написания правильного асинхронного кода.
JavaScript: однопоточный язык
JavaScript имеет один поток выполнения (главный поток), поэтому ему нужен механизм для выполнения асинхронных операций без блокировки:
console.log('1');
setTimeout(() => {
console.log('2');
}, 1000);
console.log('3');
// Вывод:
// 1
// 3
// 2 (через 1 секунду)
Это работает потому что setTimeout не блокирует выполнение кода.
Call Stack (стек вызовов)
Все синхронные операции выполняются в call stack:
function a() {
b();
}
function b() {
c();
}
function c() {
console.log('c');
}
a();
// Стек вызовов:
// 1. a() — добавляется в стек
// 2. b() — добавляется в стек (внутри a)
// 3. c() — добавляется в стек (внутри b)
// 4. console.log — выполняется
// 5. c() выходит из стека
// 6. b() выходит из стека
// 7. a() выходит из стека
Event Loop
Event Loop — это цикл, который проверяет стек вызовов и очередь callbacks:
console.log('Начало');
setTimeout(() => {
console.log('setTimeout');
}, 0); // Даже с 0 ms выполнится позже!
console.log('Конец');
// Вывод:
// Начало
// Конец
// setTimeout
Почему так? Даже setTimeout с 0ms отправляет callback в очередь, и он выполняется только когда call stack пуст.
Web APIs и очереди
В браузере есть несколько очередей для асинхронных операций:
1. Macrotask Queue (очередь макротасков)
setTimeoutsetIntervalsetImmediate(Node.js)requestAnimationFrame- I/O операции
setTimeout(() => {
console.log('Макротаск');
}, 0);
2. Microtask Queue (очередь микротасков) — выше приоритет!
Promise(.then, .catch, .finally)async/awaitMutationObserverqueueMicrotask
Promise.resolve().then(() => {
console.log('Микротаск');
});
Порядок выполнения
console.log('1. Синхронный код');
setTimeout(() => {
console.log('2. setTimeout (макротаск)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise.then (микротаск)');
});
console.log('4. Ещё синхронный код');
// Вывод:
// 1. Синхронный код
// 4. Ещё синхронный код
// 3. Promise.then (микротаск)
// 2. setTimeout (макротаск)
Event Loop в деталях
- Выполнить весь синхронный код из call stack
- Выполнить ВСЕ микротаски (Promises, queueMicrotask)
- Выполнить один макротаск (setTimeout, setInterval)
- Проверить UI-обновления (repaint, reflow)
- Повторить с шага 2
console.log('Старт');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => console.log('Promise in setTimeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => console.log('setTimeout in Promise'), 0);
})
.then(() => console.log('Promise 2'));
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
console.log('Конец');
// Вывод:
// Старт
// Конец
// Promise 1
// Promise 2
// setTimeout 1
// Promise in setTimeout
// setTimeout 2
// setTimeout in Promise
Async/Await
Async/await работает с Promises и выполняется в microtask queue:
async function example() {
console.log('1. async функция начала');
await Promise.resolve();
console.log('2. После await (микротаск)');
}
console.log('0. Начало');
example();
console.log('3. После вызова async');
// Вывод:
// 0. Начало
// 1. async функция начала
// 3. После вызова async
// 2. После await (микротаск)
Fetch API и Promises
Fetch возвращает Promise, поэтому использует microtask queue:
console.log('Старт');
fetch('/api/data')
.then(response => response.json())
.then(data => console.log('Данные:', data));
setTimeout(() => {
console.log('setTimeout');
}, 0);
console.log('Конец');
// Вывод:
// Старт
// Конец
// Данные: ... (когда fetch завершится)
// setTimeout
Практический пример: Race condition
let result = null;
fetch('/api/data')
.then(response => response.json())
.then(data => {
result = data; // Микротаск — выполнится быстро
});
setTimeout(() => {
console.log(result); // Макротаск — выполнится позже
}, 1000);
// result будет заполнен в 999ms, выведется в 1000ms
requestAnimationFrame
Это специальный макротаск, выполняется перед перерисовкой экрана:
console.log('Старт');
requestAnimationFrame(() => {
console.log('requestAnimationFrame');
});
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('Конец');
// Вывод:
// Старт
// Конец
// Promise
// requestAnimationFrame
// setTimeout
Как отладить асинхронный код
// 1. Используй async/await (более понятно)
async function main() {
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
console.log(posts);
} catch (error) {
console.error('Ошибка:', error);
}
}
// 2. Добавляй console.log с временем
console.time('loading');
await fetchData();
console.timeEnd('loading');
// 3. Используй DevTools для профилирования
// Chrome DevTools → Performance → Record
Ключевые моменты
- JavaScript однопоточный, но Web APIs позволяют работать асинхронно
- Event Loop управляет порядком выполнения кода
- Микротаски (Promises) выполняются раньше макротасков (setTimeout)
- Call stack должен быть пуст перед выполнением callbacks
- Async/await — синтаксический сахар над Promises
- requestAnimationFrame выполняется перед перерисовкой экрана
Понимание event loop критично для написания эффективного асинхронного JavaScript кода и предотвращения race conditions.