Skip to main content

Вкладені таймери

Коротко: багато "вкладених" setTimeout (коли в колбеку ставиться наступний таймер) не переповнюють стек, але можуть забити чергу задач, спричинити дрейф часу, троттлінг і лаг інтерфейсу.

Що саме відбувається

  1. Немає stack overflow. Кожен setTimeout ставить наступну задачу в чергу макрозадач і завершує поточну - стек очищується між викликами.
  2. Черга макрозадач росте. Якщо всередині кожного колбека ставити кілька нових таймерів або виконувати важку роботу, задачі почнуть накопичуватися, а UI - підгальмовувати.
  3. Дрейф (накопичення затримки). Час "цокання" зміщується: колбек виконається не раніше зазначеної затримки і ще пізніше, якщо стек був зайнятий. При довгих ланцюжках зміщення накопичується.
  4. Мінімальна затримка і троттлінг. Навіть при 0 мс браузери застосовують мінімальний інтервал (зазвичай ≈4 мс; у неактивній вкладці - до ~1000 мс і більше). Вкладені таймери з надмалими затримками все одно будуть "зажиматися".
  5. Мікрозадачі між таймерами. Після кожного колбека виконуються всі мікрозадачі (Promise.then, queueMicrotask). Якщо ви всередині таймера створюєте багато мікрозадач, вони будуть додатково подовжувати кроки ланцюжка.
  6. Порядок виконання. Таймери беруться з черги за часом готовності; при "одночасній готовності" порядок може відрізнятися між рушіями, тому на конкретний порядок краще не покладатися.
  7. Пам'ять. Якщо колбеки замикають великі об'єкти, а ланцюжок довгий/нескінченний, можливе зростання споживання пам'яті (об'єкти довше залишаються досяжними).

Приклад "вкладеного" ланцюжка

javascript
function tick() { // робота... setTimeout(tick, 0); // наступний тік - у наступному обороті Event Loop } setTimeout(tick, 0);
  • Стек не росте, але "тіки" будуть йти з обмеженням мінімальної затримки і з дрейфом.

Як робити краще

  • Компенсуйте дрейф для періодичних задач:

    javascript
    const start = performance.now(); const step = 1000; // 1 сек let n = 0; function loop() { n++; // ...корисна робота... const nextAt = start + n * step; setTimeout(loop, Math.max(0, nextAt - performance.now())); } setTimeout(loop, step);
  • Для анімацій використовуйте requestAnimationFrame - він синхронізований з перемальовуванням і менше "дрейфує".

  • Для "кожні N мс" без накладень надавайте перевагу рекурсивному setTimeout (як вище), а не setInterval - так ви уникнете накопичення, якщо колбек іноді довший за інтервал.

  • Важку роботу виносьте у Web Worker, щоб не блокувати UI.

Висновок

Вкладені setTimeout - безпечний спосіб "розірвати" синхронну рекурсію, але:

  • точного тайминга не буде,
  • при великому обсязі задач ви отримаєте лаг і дрейф,
  • браузер введе мінімальні затримки і троттлінг.

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

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

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