Skip to main content

setTimeout(fn, 0)

setTimeout(fn, 0) не виконує функцію негайно, як може здатися: він ставить колбек fn у чергу на виконання після поточного коду, тобто асинхронно, у наступному циклі event loop. Нульова затримка це не «виконати зараз», а «виконати якнайшвидше, але після всього синхронного коду».

Теорія

TL;DR

  • setTimeout(fn, 0) планує виклик, а не виконує його.
  • Колбек виконається після того, як звільниться call stack, тобто після всього поточного синхронного коду.
  • Фактична затримка більша за нуль: специфікація дозволяє браузеру мінімум близько 4 мс для вкладених таймерів.
  • Причина в тому, що JavaScript однопотоковий: асинхронні колбеки чекають у черзі.
  • Використовують, щоб відкласти код, не блокувати UI і дати браузеру оновити DOM.

Швидкий приклад

javascript
setTimeout(() => console.log('timeout'), 0); console.log('main');

Вивід:

text
main timeout

Хоча затримка дорівнює нулю, JavaScript спочатку виконує весь поточний стек, тобто все синхронне, і лише потім бере колбеки з черги таймерів (callback queue).

Чому так

JavaScript однопотоковий: у нього один call stack і одна черга задач. Усі асинхронні операції (setTimeout, fetch, Promise) потрапляють у чергу подій і виконуються тільки після того, як стек викликів звільниться. Event loop постійно перевіряє: якщо стек порожній, він бере наступну задачу з черги і кладе її в стек.

javascript
console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C');

Вивід:

text
A C B

Нуль насправді не нуль

Затримка в setTimeout це мінімальний час очікування, а не гарантований. Колбек не запуститься, доки стек зайнятий, тому важка синхронна операція легко відкладе його на сотні мілісекунд.

javascript
setTimeout(() => console.log('timeout'), 0); const end = Date.now() + 500; while (Date.now() < end) { // busy loop blocks the stack for half a second } console.log('main'); // main first, timeout only after the loop is over

Додатково браузери обмежують вкладені таймери: починаючи приблизно з п'ятого рівня вкладеності мінімальна затримка стає близько 4 мс. У неактивній вкладці інтервал може бути ще більшим.

Для чого використовують setTimeout(fn, 0)

  • Дочекатися завершення поточних операцій, перш ніж читати результат.
  • Не блокувати інтерфейс: розбити важке обчислення на частини, між якими браузер встигне відмалювати кадр.
  • Запустити код «після всього іншого», наприклад після того, як спрацюють усі обробники події.
  • Дати браузеру оновити DOM перед наступним кроком.
javascript
button.addEventListener('click', () => { status.textContent = 'Loading...'; // the browser can paint this first setTimeout(() => { runHeavyCalculation(); // deferred, so the text is visible }, 0); });

Для роботи з анімацією краще підходить requestAnimationFrame, а для відкладання до наступної мікрозадачі, queueMicrotask або Promise.resolve().then(fn).

Підсумкова таблиця

Що робитьСтавить колбек у чергу на наступний цикл подій
Час затримкиМінімум 0 мс, фактично трохи пізніше
ВиконанняПісля всього синхронного коду
Використовують дляВідкладеного коду, синхронізації, уникнення блокування UI

Типові помилки

  • Вважати, що setTimeout(fn, 0) виконає функцію негайно і результат буде доступний на наступному рядку.
  • Писати setTimeout(fn(), 0): дужки виконують функцію одразу і передають таймеру undefined.
  • Розраховувати на точну затримку: 0 це мінімум, реальний час залежить від зайнятості стека і від браузера.
  • Плутати з мікрозадачами: Promise.resolve().then(fn) виконається раніше за setTimeout(fn, 0).
  • Використовувати setTimeout(fn, 0) для анімації замість requestAnimationFrame і отримувати ривки.
  • Не зберігати ідентифікатор таймера, коли виклик треба мати змогу скасувати через clearTimeout.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.