← Назад к вопросам

Как распределяются по очереди задач 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);

Ключевые моменты

  1. setTimeout и setInterval добавляются в MACROTASK QUEUE
  2. Microtask (Promise) выполняются ДО macrotask
  3. Event loop обрабатывает по одному макротаску за раз
  4. Минимальная задержка в браузере ~4ms
  5. setInterval может пропускать вызовы если код блокирует
  6. requestAnimationFrame лучше для анимаций
  7. В фоновых вкладках setTimeout/setInterval throttle до 1Hz
  8. Используй рекурсивный setTimeout для гарантированных интервалов

Понимание очереди задач критично для написания производительного асинхронного кода.