Вкладені таймери
Коротко: багато "вкладених" setTimeout (коли в колбеку ставиться наступний таймер) не переповнюють стек, але можуть забити чергу задач, спричинити дрейф часу, троттлінг і лаг інтерфейсу.
Що саме відбувається
- Немає stack overflow.
Кожен
setTimeoutставить наступну задачу в чергу макрозадач і завершує поточну - стек очищується між викликами. - Черга макрозадач росте. Якщо всередині кожного колбека ставити кілька нових таймерів або виконувати важку роботу, задачі почнуть накопичуватися, а UI - підгальмовувати.
- Дрейф (накопичення затримки). Час "цокання" зміщується: колбек виконається не раніше зазначеної затримки і ще пізніше, якщо стек був зайнятий. При довгих ланцюжках зміщення накопичується.
- Мінімальна затримка і троттлінг.
Навіть при
0мс браузери застосовують мінімальний інтервал (зазвичай ≈4 мс; у неактивній вкладці - до ~1000 мс і більше). Вкладені таймери з надмалими затримками все одно будуть "зажиматися". - Мікрозадачі між таймерами.
Після кожного колбека виконуються всі мікрозадачі (
Promise.then,queueMicrotask). Якщо ви всередині таймера створюєте багато мікрозадач, вони будуть додатково подовжувати кроки ланцюжка. - Порядок виконання. Таймери беруться з черги за часом готовності; при "одночасній готовності" порядок може відрізнятися між рушіями, тому на конкретний порядок краще не покладатися.
- Пам'ять. Якщо колбеки замикають великі об'єкти, а ланцюжок довгий/нескінченний, можливе зростання споживання пам'яті (об'єкти довше залишаються досяжними).
Приклад "вкладеного" ланцюжка
javascript
function tick() {
// робота...
setTimeout(tick, 0); // наступний тік - у наступному обороті Event Loop
}
setTimeout(tick, 0);- Стек не росте, але "тіки" будуть йти з обмеженням мінімальної затримки і з дрейфом.
Як робити краще
-
Компенсуйте дрейф для періодичних задач:
javascriptconst 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
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.