Чому await зупиняє лише поточну функцію
await не блокує потік виконання JavaScript, він призупиняє лише ту async-функцію, у якій стоїть. Решта програми, обробники подій, таймери й інші async-функції продовжують працювати, бо await віддає керування назад у Event Loop.
Теорія
TL;DR
- JavaScript виконує код в одному потоці, але керує задачами через черги Event Loop.
awaitне зупиняє цей потік, він «заморожує» тільки поточнуasync-функцію.- Решту тіла функції рушій загортає в мікротаску, яка виконається після завершення промісу.
- Стек викликів звільняється, тому інший код (таймери, події, інші функції) виконується далі.
- Коли проміс переходить у
fulfilledабоrejected, функція продовжується рівно з місцяawait. - Це асинхронна пауза, а не блокування: жодних потоків ОС чи
sleepтут немає.
Швидкий приклад
async function task() {
console.log('Початок');
await new Promise(r => setTimeout(r, 2000));
console.log('Кінець');
}
task();
// Цей рядок виконається одразу, рушій не заблокований
console.log('Цей код виконується одразу!');Вивід:
Початок
Цей код виконується одразу!
(через 2 с)
Кінецьawait призупиняє тільки функцію task(), а основний код іде далі. JS не «зависає»: усередині task() просто створюється мікротаска, яка повернеться в чергу після спрацювання таймера.
Що відбувається під капотом
Коли рушій зустрічає await promise, відбувається таке:
- Виконання поточної
async-функції призупиняється. - Рушій створює мікротаску (callback), яка має продовжити функцію після завершення промісу.
- Керування повертається в Event Loop, стек викликів звільняється.
- Рушій виконує інші задачі: інші функції, обробники подій, таймери тощо.
- Коли проміс завершено (
fulfilledабоrejected), мікротаска виконується і функція відновлюється саме з того рядка, де стоявawait.
Усе це реалізовано через чергу мікротасок (PromiseJobs). Ніяких додаткових потоків чи справжнього sleep рушій не використовує.
Крок за кроком в Event Loop
| Етап | Що відбувається |
|---|---|
| 1 | task() потрапляє в Call Stack і виконує код до await |
| 2 | На await функція «заморожується», створюється мікротаска |
| 3 | Стек очищується, інші скрипти продовжують виконуватися |
| 4 | Коли проміс завершується, мікротаска повертається в чергу |
| 5 | Після поточного тіку Event Loop виконується решта коду task() |
Якби await справді блокував потік, увесь JavaScript «завис» би на ці 2 секунди: не спрацював би жоден обробник подій і жоден setTimeout. Але такого не стається: поки task() чекає, Event Loop спокійно обробляє інші події.
Паралельні async-функції не чекають одна одну
async function foo() {
console.log('foo start');
await new Promise(res => setTimeout(res, 1000));
console.log('foo end');
}
async function bar() {
console.log('bar start');
await new Promise(res => setTimeout(res, 500));
console.log('bar end');
}
foo();
bar();
console.log('main thread');Вивід:
foo start
bar start
main thread
(через 0.5 с) bar end
(через 1 с) foo endawait не заважає виконанню інших функцій. Кожна функція просто «заморожується» до свого промісу, а потім продовжує роботу незалежно від сусідньої.
Мікротаска відновлення і порядок виводу
async function demo() {
console.log('A');
await Promise.resolve();
console.log('B');
}
demo();
console.log('C');Результат:
A
C
BЧому саме так:
Aвиконується синхронно, доawait;awaitзупиняє функцію і ставить її продовження (B) у чергу мікротасок;- основний код доходить до
C; - і лише коли стек порожній, рушій виконує мікротаски, тобто
B.
Проста аналогія:
async-функція, це серіал. Наawaitконкретний серіал ставиться на паузу, але інші канали продовжують мовлення. Коли потрібний проміс готовий, серія йде далі рівно з того кадру, на якому зупинилася.
Що await робить і чого не робить
Що робить await | Чого не робить |
|---|---|
Призупиняє лише поточну async-функцію | Не блокує потік JavaScript |
| Повертає керування в Event Loop | Не заважає іншим задачам виконуватися |
| Після завершення промісу відновлює виконання | Не «заморожує» весь застосунок |
Реалізований через мікротаски (PromiseJobs) | Не використовує потоки ОС чи sleep |
Типові помилки
- Вважати, що
awaitзупиняє всю сторінку, і через це уникати його в коді, який реагує на події. - Плутати паузу функції з блокуванням: справжнє блокування дає синхронний цикл
while (Date.now() < end) {}, і саме він «вішає» вкладку, бо не віддає стек. - Ставити
awaitу циклі там, де запити незалежні: функція чекає їх послідовно, хоча можна запустити все разом черезPromise.all(). - Очікувати, що два
awaitпідряд виконаються паралельно. Вони строго послідовні: другий стартує лише після завершення першого. - Забувати, що після
awaitкод продовжується вже в мікротасці, тому порядок логів відрізняється від синхронного читання зверху вниз.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.