Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Гарантія виконання таймера». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Ні, точний час виконання таймера гарантувати неможливо.** `setTimeout` і `setInterval` не обіцяють, що колбек виконається рівно через `delay` мс: вони лише ставлять задачу в чергу і гарантують, що раніше за `delay` вона не виконається. JavaScript однопотоковий, тому доки стек викликів зайнятий, колбек чекає; додайте сюди мінімальний поріг браузера (близько 4 мс), throttling у неактивній вкладці (до 1000 мс і більше), навантаження на CPU і планувальник операційної системи. Для точності беруть `performance.now()`, `requestAnimationFrame()` або таймер із компенсацією дрейфу. ```javascript setTimeout(() => console.log('1s'), 1000); const start = Date.now(); while (Date.now() - start < 3000) {} // спрацює лише через 3 секунди ``` **Ключове:** затримка таймера це мінімум, а не точний час; для точних вимірювань використовуйте `performance.now()` і `requestAnimationFrame()`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Ні, гарантувати точний час виконання таймера в JavaScript (`setTimeout`, `setInterval` тощо) неможливо.** Причина проста: JavaScript однопотоковий, і момент запуску таймера залежить від стану Event Loop і завантаженості стека викликів. ## Теорія ### TL;DR - Таймер гарантує лише те, що колбек не виконається **раніше** за вказану затримку. - Поки стек викликів зайнятий, колбек чекає у черзі макрозадач. - Навіть `setTimeout(fn, 0)` має мінімальну затримку, у браузерах це близько 4 мс. - У неактивній вкладці браузер навмисно сповільнює таймери, до 1000 мс і більше. - `setInterval` теж не дає рівних інтервалів: повільний колбек зсуває всі наступні тики. - Для точності є `performance.now()`, `requestAnimationFrame()` і схема з компенсацією дрейфу. ### Швидкий приклад ```javascript console.log('Старт'); setTimeout(() => console.log('Таймер 1s'), 1000); const start = Date.now(); while (Date.now() - start < 3000) {} // блокуємо потік на 3 секунди console.log('Кінець'); ``` Результат: ```text Старт (3 секунди паузи) Кінець Таймер 1s ``` Хоча таймер був на 1 секунду, він спрацював лише **через 3 секунди**. Чому? Тому що рушій **не міг виконати його раніше**, стек був зайнятий циклом. ### Чому так відбувається Коли ви викликаєте: ```javascript setTimeout(fn, 1000); ``` JavaScript **не обіцяє**, що `fn` виконається **рівно через 1000 мс**. Він лише **ставить задачу в чергу**, щоб виконати її **не раніше**, ніж через 1000 мс. Якщо в момент, коли таймер «спрацював», стек викликів зайнятий, колбек чекатиме своєї черги. ### Мінімальна затримка і throttling Навіть `setTimeout(fn, 0)` не запускає код «одразу», мінімальна затримка все одно є: - у браузерах зазвичай **4 мс** для вкладених таймерів, за стандартом HTML Living Standard; - у Node.js вона може бути меншою, але теж **не миттєва**. Крім того, якщо вкладка браузера неактивна, затримку може бути збільшено до 1000 мс і більше: браузери **економлять ресурси**. ### Приклад із setInterval `setInterval()` **теж не гарантує** точних інтервалів. Якщо функція всередині виконується довше, ніж сам інтервал, виклики «накопичуються» і виконуються із запізненням. ```javascript setInterval(() => { const start = Date.now(); while (Date.now() - start < 1500) {} // блокуємо на 1.5 секунди console.log('tick'); }, 1000); ``` Інтервал 1 секунда, але функція займає 1.5 секунди, тому «тіки» йдуть із реальною затримкою 1.5, 3.0, 4.5 секунди і так далі. ### Чому точність недосяжна | Причина | Пояснення | | --- | --- | | **Однопотоковість JavaScript** | Поки виконується код, інші задачі чекають | | **Event Loop** | Таймери лише потрапляють у чергу, вони не запускаються миттєво | | **Обмеження браузера** | Є мінімальна затримка, а у фоні ще й сповільнення | | **Навантаження на CPU** | За високого навантаження таймери виконуються пізніше | | **Планувальник ОС** | JavaScript не керує системним часом, він лише «просить» запустити задачу пізніше | ### Що можна зробити Якщо потрібна **максимальна точність**, наприклад в анімації або синхронізації, є альтернативи. 1. `performance.now()` вимірює точний час у мілісекундах, із часткою до мікросекунд: ```javascript const start = performance.now(); // ... const elapsed = performance.now() - start; ``` 2. `requestAnimationFrame()` запускає колбек перед кожним кадром, приблизно 60 разів на секунду, і ідеально підходить для плавних анімацій. 3. `setTimeout` **з компенсацією дрейфу**, тобто з корекцією затримки: ```javascript const start = Date.now(); let count = 0; function tick() { count++; const expected = start + count * 1000; const drift = Date.now() - expected; console.log(`tick ${count}, drift: ${drift}ms`); setTimeout(tick, 1000 - drift); } tick(); ``` Такий підхід враховує «запізнення» і вирівнює інтервали. ### Підсумок | Питання | Відповідь | | --- | --- | | Чи можна гарантувати точний час таймера? | Ні | | Чому? | Однопотоковість JavaScript, Event Loop, мінімальні затримки і навантаження | | Що відбувається насправді? | Таймер додається у чергу задач і виконується пізніше | | Чи є альтернатива для точності? | Так: `requestAnimationFrame`, `performance.now`, компенсація дрейфу | ### Типові помилки - **Вважати затримку розкладом.** `delay` це нижня межа, а не момент запуску. - **Будувати годинник на `setInterval`.** За годину такий лічильник помітно відстане; беріть за основу реальний час через `Date.now()` або `performance.now()` і перемальовуйте значення. - **Вимірювати тривалість через `Date.now()`.** Системний час може змінитися, зокрема під час синхронізації; `performance.now()` монотонний і для вимірювань надійніший. - **Забувати про фонові вкладки.** Логіка, що спирається на рівний тик таймера, ламається, щойно користувач перемкнув вкладку. - **Анімувати через `setInterval`.** Кадрову анімацію робить `requestAnimationFrame`, він синхронізований з оновленням екрана і не дає розривів.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.