setTimeout(fn, 0)
setTimeout(fn, 0) не виконує функцію миттєво: він просто ставить колбек fn у чергу макрозадач, щоб той виконався після завершення поточного коду і всіх мікрозадач. Нульова затримка означає «у наступній ітерації Event Loop», а не «зараз».
Теорія
TL;DR
setTimeout(fn, 0)ставитьfnу чергу макрозадач, а не виконує його одразу.- Колбек чекає, доки звільниться стек викликів, бо Event Loop ніколи не перериває код, що вже виконується.
- Перед ним встигнуть виконатися всі мікрозадачі:
Promise.then,queueMicrotask. - Порядок завжди такий: стек викликів, потім мікрозадачі, потім
setTimeout. - Це стандартний спосіб відкласти роботу, розірвати довгий синхронний блок і дати інтерфейсу оновитися.
- Виклик повертає ID таймера, який можна скасувати через
clearTimeout.
Швидкий приклад
console.log('A');
setTimeout(() => console.log('B'), 0);
console.log('C');Вивід:
A
C
BЧому не A, потім B, потім C? Бо колбек таймера не має права втрутитися в код, який уже виконується.
Що відбувається крок за кроком
console.log('A')виконується одразу, у стеку викликів (Call Stack).setTimeout(() => console.log('B'), 0)передає задачу у Web API, це таймер із затримкою0мс.- Таймер миттєво «спрацьовує», і колбек додається у чергу макрозадач.
- JavaScript продовжує виконувати поточний стек, тобто друкує
C. - Коли стек викликів порожній, Event Loop бере задачу
() => console.log('B')з черги і виконує її.
Чому навіть 0 не виконується одразу
Тому що в JavaScript усе проходить через Event Loop, а він ніколи не перериває поточний стек викликів.
Навіть якщо затримка дорівнює
0, колбек все одно мусить дочекатися, поки стек звільниться.
Тобто setTimeout(fn, 0) це спосіб відкласти виконання функції до наступної ітерації Event Loop.
Варто пам'ятати ще одну деталь: за специфікацією браузери додають мінімальний поріг приблизно 4 мс для вкладених таймерів, тому реальна затримка майже ніколи не дорівнює нулю.
Приклад із промісом (мікрозадача)
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');Порядок:
end
promise
timeoutПояснення:
Promise.then()створює мікрозадачу;setTimeout()створює макрозадачу;- а мікрозадачі завжди виконуються раніше за макрозадачі.
Практичне застосування
-
Відкласти виконання, щоб не блокувати основний код:
javascriptsetTimeout(() => { console.log('Виконається після основного коду'); }, 0); -
Розірвати синхронний цикл, щоб інтерфейс встиг оновитися:
javascriptfor (let i = 0; i < 1e9; i++) {} // важка операція console.log('Інтерфейс підвис...'); setTimeout(() => { console.log('Тепер виконуємося після паузи'); }, 0); -
Скинути стек, наприклад у рекурсивних обчисленнях:
javascriptfunction nextStep() { setTimeout(nextStep, 0); // кожне повторення це нова ітерація Event Loop } nextStep();
Підсумок
| Параметр | Значення |
|---|---|
| Що робить | Ставить колбек у чергу макрозадач |
| Коли виконується | Після поточного стека і всіх мікрозадач |
| Затримка «0» | Не миттєво, а в наступному циклі Event Loop |
| Черга | Макрозадачі |
| Повертає | ID таймера, який можна скасувати через clearTimeout |
Візуально це виглядає так:
[Call Stack] -> виконується поточний код
[Microtask Queue] -> проміси і queueMicrotask
[Task Queue] -> setTimeout(fn, 0)setTimeout(fn, 0) завжди чекає, поки:
- очиститься стек викликів,
- виконаються мікрозадачі,
- і лише потім виконується
fn.
Типові помилки
- Думати, що
0означає «негайно». Це означає «якнайшвидше після звільнення стека», різниця критична, коли поруч є важкий синхронний код. - Ставити
setTimeout(fn, 0)в один ряд ізPromise.resolve().then(fn). Проміс це мікрозадача, вона завжди випередить таймер. - Розраховувати на рівно 0 мс. Браузери застосовують мінімальний поріг близько 4 мс для вкладених таймерів, а у фоновій вкладці затримки ще більші.
- Лікувати нульовим таймером зависання інтерфейсу. Він лише дає браузеру шанс перемалювати екран між шматками роботи; якщо сама робота важка, її треба розбити на частини або винести у Web Worker.
- Забувати про
clearTimeout. Відкладений колбек може спрацювати вже після того, як компонент знято з екрана, і звернутися до неактуальних даних.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.