← Назад к вопросам
Как распределяются по очереди задач setInterval, setTimeout?
2.2 Middle🔥 143 комментариев
#JavaScript Core
Комментарии (3)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как распределяются по очереди задач setInterval и setTimeout
setTimeout и setInterval работают с event loop браузера. Это асинхронные функции, которые добавляют задачи в очередь обработки. Понимание как они работают критично для оптимизации производительности.
Event Loop - основа понимания
// Event Loop выполняет задачи в таком порядке:
// 1. Синхронный код (call stack)
// 2. Microtask queue (Promise.then, queueMicrotask)
// 3. Render (если нужно)
// 4. Macrotask queue (setTimeout, setInterval)
console.log('1. Start');
setTimeout(() => {
console.log('4. setTimeout callback');
}, 0);
Promise.resolve()
.then(() => {
console.log('3. Promise callback');
});
console.log('2. End');
// Вывод:
// 1. Start
// 2. End
// 3. Promise callback
// 4. setTimeout callback
setTimeout - макротаск
// setTimeout добавляет функцию в MACROTASK QUEUE
setTimeout(() => {
console.log('This runs later');
}, 1000);
// Минимальная задержка 4ms в браузере для вложенных таймеров
setTimeout(() => {
console.log('Even with 0ms, waits 4ms minimum');
}, 0);
// Пример с очередью
console.log('Start');
setTimeout(() => console.log('A'), 0);
setTimeout(() => console.log('B'), 0);
setTimeout(() => console.log('C'), 0);
console.log('End');
// Вывод:
// Start
// End
// A
// B
// C
setInterval - повторяющийся макротаск
// setInterval добавляет функцию в queue каждый N миллисекунд
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Interval fired ${count} times`);
if (count >= 3) {
clearInterval(intervalId);
}
}, 1000);
// Остановить интервал
// clearInterval(intervalId);
Разница между setTimeout и setInterval
// setTimeout - выполнится один раз через N миллисекунд
const id = setTimeout(() => {
console.log('Runs once');
}, 1000);
// Можно отменить
clearTimeout(id);
// setInterval - выполнится каждые N миллисекунд бесконечно
const id = setInterval(() => {
console.log('Runs every second');
}, 1000);
// Нужно отменить явно
clearInterval(id);
Очередь макротасков
console.log('1');
setTimeout(() => console.log('2'), 0);
setTimeout(() => console.log('3'), 0);
setTimeout(() => console.log('4'), 0);
console.log('5');
// Макротаски выполняются по одному за раз
// Вывод:
// 1
// 5
// 2 <- первый setTimeout из очереди
// 3 <- второй setTimeout из очереди
// 4 <- третий setTimeout из очереди
// Если в каждом setTimeout есть ещё setTimeout
setTimeout(() => {
console.log('A');
setTimeout(() => console.log('A2'), 0);
}, 0);
setTimeout(() => {
console.log('B');
setTimeout(() => console.log('B2'), 0);
}, 0);
// Вывод:
// A
// B
// A2
// B2
Microtask vs Macrotask
// MICROTASK QUEUE (выполняется перед render и macrotask)
// - Promise.then/catch/finally
// - queueMicrotask()
// - MutationObserver
// - process.nextTick (Node.js)
// MACROTASK QUEUE (выполняется после microtask и render)
// - setTimeout
// - setInterval
// - setImmediate (Node.js, не в браузере)
// - requestAnimationFrame (асинхронное)
console.log('Start');
setTimeout(() => {
console.log('setTimeout (macrotask)');
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise (microtask)');
});
queueMicrotask(() => {
console.log('queueMicrotask (microtask)');
});
console.log('End');
// Вывод:
// Start
// End
// Promise (microtask)
// queueMicrotask (microtask)
// setTimeout (macrotask)
Практический пример со сложной очередью
console.log('1');
setTimeout(() => {
console.log('2 - setTimeout');
Promise.resolve().then(() => {
console.log('3 - Promise внутри setTimeout');
});
}, 0);
Promise.resolve()
.then(() => {
console.log('4 - Promise 1');
setTimeout(() => {
console.log('5 - setTimeout внутри Promise');
}, 0);
})
.then(() => {
console.log('6 - Promise 2');
});
queueMicrotask(() => {
console.log('7 - queueMicrotask');
});
console.log('8');
// Вывод:
// 1
// 8
// 4 - Promise 1
// 7 - queueMicrotask
// 6 - Promise 2
// 2 - setTimeout
// 3 - Promise внутри setTimeout
// 5 - setTimeout внутри Promise
Оптимизация с requestAnimationFrame
// requestAnimationFrame выполняется ДО следующего render
// Лучше для анимаций чем setTimeout
let y = 0;
function animate() {
y += 5;
element.style.transform = `translateY(${y}px)`;
if (y < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// vs
let y = 0;
function animate() {
y += 5;
element.style.transform = `translateY(${y}px)`;
if (y < 500) {
setTimeout(animate, 16); // ~60fps но не гарантировано
}
}
setTimeout(animate, 16);
Проблема с setInterval и Blocking код
// ПРОБЛЕМА: Если выполнение длится дольше интервала
setInterval(() => {
heavyCalculation(); // занимает 2 секунды
console.log('Done');
}, 1000);
// Если heavyCalculation длится 2 сек, то задачи скапливаются
// Они не будут пропущены, а выполнены одна за другой
// РЕШЕНИЕ: Использовать рекурсивный setTimeout
function recursiveTimer() {
heavyCalculation();
console.log('Done');
setTimeout(recursiveTimer, 1000); // Ждёт ДО КОНЦА выполнения
}
setTimeout(recursiveTimer, 1000);
// Или использовать Web Workers для тяжёлых вычислений
Производительность
// Вкладка в фоне - throttling
// Браузер замедляет setTimeout/setInterval до 1Hz (раз в секунду)
// Это экономит батарею на мобильных
// Минимальные задержки:
// setTimeout: 4ms minimum
// setInterval: 4ms minimum
// Вложенный setTimeout (> 5 уровней): 4ms minimum
setTimeout(() => {
const start = performance.now();
setTimeout(() => {
const elapsed = performance.now() - start;
console.log(elapsed); // минимум ~4ms
}, 0);
}, 0);
Практический пример - Rate limiting
function throttleWithSetInterval(fn, delay) {
let lastCall = 0;
let intervalId = null;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
fn.apply(this, args);
lastCall = now;
} else if (!intervalId) {
intervalId = setTimeout(() => {
fn.apply(this, args);
lastCall = Date.now();
intervalId = null;
}, delay - (now - lastCall));
}
};
}
const throttledScroll = throttleWithSetInterval(() => {
console.log('Scroll handled');
}, 1000);
window.addEventListener('scroll', throttledScroll);
Ключевые моменты
- setTimeout и setInterval добавляются в MACROTASK QUEUE
- Microtask (Promise) выполняются ДО macrotask
- Event loop обрабатывает по одному макротаску за раз
- Минимальная задержка в браузере ~4ms
- setInterval может пропускать вызовы если код блокирует
- requestAnimationFrame лучше для анимаций
- В фоновых вкладках setTimeout/setInterval throttle до 1Hz
- Используй рекурсивный setTimeout для гарантированных интервалов
Понимание очереди задач критично для написания производительного асинхронного кода.