setTimeout(fn, 0) означає не «виконай зараз», а «постав у чергу, щойно буде можливість». JavaScript у браузері виконується в одному потоці: поки працює поточний код, ніщо інше не запуститься.
console.log('1');
setTimeout(() => console.log('3'), 0);
Promise.resolve().then(() => console.log('2б'));
console.log('2');
// 1, 2, 2б, 3
Порядок такий:
- виконується весь синхронний код до кінця;
- виконуються всі мікрозадачі (колбеки Promise,
queueMicrotask); - браузер може перемалювати сторінку;
- береться наступна макрозадача - зокрема колбек таймера.
Тому колбек Promise спрацьовує раніше за таймер з нульовою затримкою.
Затримка - мінімальна, а не точна. setTimeout(fn, 100) гарантує лише, що колбек запуститься не раніше ніж за 100 мс. Якщо головний потік зайнятий важким обчисленням 2 секунди, таймер спрацює через 2 секунди.
Інші обмеження:
- вкладені таймери після п'яти рівнів отримують мінімальну затримку 4 мс;
- у фонових вкладках браузер пригальмовує таймери до разу на секунду і рідше - не можна покладатися на них для точного відліку часу;
- для анімацій є
requestAnimationFrame, синхронізований з перемальовуванням екрана.
Навіщо setTimeout(fn, 0) на практиці: відкласти роботу до моменту, коли поточна подія оброблена й DOM оновлено, або розбити довгу роботу на шматки, даючи браузеру перемальовувати сторінку між ними.