Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «setTimeout і setInterval в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)`setTimeout` виконує колбек один раз після мінімальної затримки; `setInterval` повторює виклик кожен інтервал, поки ти його не зупиниш. ```javascript setTimeout(() => console.log("Один раз"), 1000); const id = setInterval(() => console.log("Тік"), 1000); clearInterval(id); // зупинити ``` **Ключове:** Обидві затримки - мінімум, не гарантія. Для асинхронних колбеків використовуй ланцюжок `setTimeout` замість `setInterval`, щоб уникнути накладання викликів.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**setTimeout** планує виконання колбека один раз після вказаної затримки; **setInterval** викликає його повторно з кожним тиком інтервалу, поки ти його не зупиниш. ## Теорія ### TL;DR - `setTimeout` спрацьовує один раз, як таймер для однієї піци. `setInterval` дзвонить постійно, як будильник кожні 5 хвилин, поки ти його не вимкнеш. - Головна різниця: одноразовий проти повторюваного. - Потрібно виконати щось один раз? `setTimeout`. Потрібен цикл? `setInterval`, але для асинхронних задач краще ланцюжок `setTimeout`. - Обидва повертають ID для `clearTimeout` або `clearInterval`. - Затримка - це мінімум, не гарантія. Event loop може додати більше часу. ### Швидкий приклад ```javascript // setTimeout: спрацьовує ОДИН РАЗ після затримки const timeoutId = setTimeout(() => console.log("Спрацювало один раз"), 100); // clearTimeout(timeoutId); // скасувати до спрацювання // setInterval: спрацьовує ПОВТОРНО кожен інтервал const intervalId = setInterval(() => console.log("Тік"), 1000); setTimeout(() => clearInterval(intervalId), 5000); // зупинити після ~5 тиків ``` `setTimeout` додає одну задачу в чергу. `setInterval` додає нову задачу кожен інтервал, незалежно від того, чи завершилася попередня. ### Ключова різниця `setInterval` планує наступний виклик одразу, як тільки минає інтервал. Він не чекає, поки колбек завершиться. Якщо колбек виконується 1.5 секунди, а інтервал - 1 секунда, виклики починають накопичуватися в черзі. `setTimeout` спрацьовує один раз і зупиняється. Якщо потрібен повторюваний таймер, який чекає завершення колбека, роби ланцюжок `setTimeout` всередині самого себе. ### Коли що використовувати - Приховати спінер після затримки: `setTimeout`. - Опитувати API кожні 5 секунд: `setInterval` для простих випадків, або ланцюжок `setTimeout` коли колбек асинхронний. - Дебаунс поля пошуку: ланцюжок `setTimeout` з `clearTimeout` на кожне натискання клавіші. - Плавна анімація 60 кадрів/с: `requestAnimationFrame`, не таймери. - Уникнути блокування UI: будь-який таймер краще за цикл `while`. ### Таблиця порівняння | Властивість | setTimeout | setInterval | |---|---|---| | Виконання | Один раз після затримки | Повторно кожен інтервал | | Мінімальна затримка | ~4мс (браузери обмежують вкладені виклики) | ~4мс, але виклики можуть накладатись | | Скасування | `clearTimeout(id)` | `clearInterval(id)` | | Безпека з async | Передбачувано | Може накопичуватись, якщо колбек повільний | | Коли використовувати | Дебаунс, одноразові затримки | Лічильники, просте опитування | ### Як браузер це обробляє `setTimeout` і `setInterval` - це не частина JavaScript-рушія. Вони живуть у шарі Web API браузера (або libuv у Node.js). Коли затримка закінчується, браузер кладе колбек у чергу макротасків. [Event loop](/questions/event-loop-in-javascript) підхоплює його тільки після того, як стек викликів і всі мікротаски (Promise) очищені. Саме тому `setTimeout(fn, 0)` не запускається миттєво. Нуль мілісекунд - це не нуль часу. Браузери також встановлюють мінімум ~4мс для вкладених викликів `setTimeout` після 5 рівнів вкладеності (специфікація HTML5). У фонових вкладках таймери можуть гальмуватися до 1000мс. ### Типові помилки **Передача виклику функції замість посилання** ```javascript function greet(name) { console.log(`Привіт ${name}`); } setTimeout(greet("Аліса"), 1000); // виконується одразу, передає undefined setTimeout(() => greet("Аліса"), 1000); // правильно ``` `greet("Аліса")` виконується негайно і повертає `undefined`. Ти передаєш цей результат у `setTimeout`, а не саму функцію. **Забутий cleanup у React компонентах** ```javascript // Витік пам'яті: інтервал продовжує жити після демонтування useEffect(() => { setInterval(() => fetchData(), 5000); }, []); // Правильно useEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); }, []); ``` Інтервал продовжує працювати після демонтування компонента. Виправлення - один рядок: повернути функцію cleanup. **setInterval з повільними асинхронними колбеками** ```javascript // Виклики можуть накластись, якщо fetch займає більше 5с setInterval(async () => { const data = await fetch("/api/status"); updateUI(data); }, 5000); // Краще: чекати завершення кожного виклику function poll() { fetch("/api/status") .then(res => res.json()) .then(data => { updateUI(data); setTimeout(poll, 5000); }); } poll(); ``` **Спроба змінити інтервал зсередини колбека** ```javascript let delay = 1000; setInterval(() => { delay *= 2; // нічого не змінює - інтервал зафіксований при плануванні }, delay); // Якщо потрібні динамічні затримки - використовуй ланцюжок setTimeout: let delay = 1000; function tick() { console.log("Тік"); delay *= 2; setTimeout(tick, delay); } setTimeout(tick, delay); ``` ### Де зустрічається у продакшені - React поле пошуку: `useEffect` + `setTimeout` для дебаунсу 300мс, `clearTimeout` у cleanup. - Redux DevTools: `setInterval` для періодичних знімків стану. - VS Code: ланцюжки `setTimeout` для дебаунсу автодоповнення при введенні. - Express/Node.js: таймери для коректного завершення сервера, `setTimeout` для примусового закриття після вікна cleanup. - Для анімацій: `requestAnimationFrame` замість `setInterval`. Для реал-тайм даних: WebSocket замість опитування. ### Питання на співбесіді **Q:** Чому `setTimeout(fn, 0)` не виконується миттєво? **A:** Колбек потрапляє в чергу макротасків. Event loop спочатку виконує весь синхронний код і мікротаски, і лише потім підхоплює його. Нульова затримка означає «якнайшвидше після поточної роботи», а не «прямо зараз». **Q:** Що таке обмеження ~4мс? **A:** Специфікація HTML5 встановлює мінімум 4мс для вкладених викликів `setTimeout` після 5 рівнів вкладеності. У фонових вкладках браузери можуть гальмувати таймери до 1000мс і більше для економії CPU. **Q:** Коли варто використовувати ланцюжок `setTimeout` замість `setInterval`? **A:** Коли колбек асинхронний або займає непередбачуваний час. Ланцюжок гарантує, що наступний виклик починається тільки після завершення попереднього, тому виклики не накладаються. **Q:** Як безпечно скасувати `setInterval`, якщо ID перехоплений у замиканні (closure)? **A:** Зберігай ID у змінній поза колбеком: `const id = setInterval(...); setTimeout(() => clearInterval(id), 5000)`. Не покладайся на те, що колбек сам збереже свій ID. **Q:** У вкладці, де задачі займають 100мс, що станеться з `setInterval(..., 10)`? **A:** Колбеки накопичуються у черзі, але не виконуються паралельно - JS однопоточний. Event loop обробляє їх по одному, тому реальні інтервали будуть близько 100мс, а не 10мс. Результат - черга, а не паралелізм. ## Приклади ### Базовий: відкладена одноразова дія ```javascript console.log("Старт"); const id = setTimeout(() => console.log("Спрацювало"), 100); console.log("Кінець"); // Вивід: Старт -> Кінець -> Спрацювало // "Кінець" виводиться до "Спрацювало", бо setTimeout асинхронний ``` `setTimeout` не блокує. Движок одразу переходить до `console.log("Кінець")` і виконує колбек пізніше з черги макротасків. ### Середній: дебаунс (debounce) поля пошуку в React ```javascript function SearchInput() { const [query, setQuery] = useState(""); useEffect(() => { const id = setTimeout(() => { if (query) fetchResults(query); }, 300); return () => clearTimeout(id); // скасувати, якщо користувач продовжує друкувати }, [query]); return <input onChange={e => setQuery(e.target.value)} />; } // Кожне натискання клавіші скидає таймер. // API-запит відправляється тільки після 300мс тиші. ``` Коли `query` змінюється, старий таймер скасовується і стартує новий. Запит іде тільки тоді, коли користувач зупинився. ### Просунутий: ланцюжок setTimeout проти setInterval для опитування API ```javascript // Версія з setInterval - може накладатись, якщо fetch повільний const id = setInterval(async () => { const res = await fetch("/api/health"); const data = await res.json(); console.log(data.status); }, 2000); // Ланцюжок setTimeout - чекає кожної відповіді function pollHealth() { fetch("/api/health") .then(r => r.json()) .then(data => { console.log(data.status); setTimeout(pollHealth, 2000); // наступний запит після завершення цього }) .catch(() => setTimeout(pollHealth, 5000)); // відступ при помилці } pollHealth(); ``` Ланцюжковий варіант дозволяє адаптувати затримку за результатом і чисто обробляти помилки. Варіант з `setInterval` може спричинити подвійні запити у продакшені, коли сервери починають гальмувати під навантаженням: один виклик ще не завершився, а наступний вже стартував.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.