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

Почему функция в setTimeout может вызваться позже чем указано время задержки?

2.0 Middle🔥 121 комментариев
#Node.js и JavaScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Event Loop и Timing в Node.js

Функция в setTimeout может вызваться существенно позже указанного времени задержки. Это одна из самых частых ошибок в понимании асинхронного программирования в JavaScript и Node.js.

Главная Причина: Event Loop

setTimeout гарантирует минимальное время задержки, но НЕ точное время.

Synchronous Code Блокирует Event Loop

console.time("setTimeout");

setTimeout(() => {
  console.log("Timeout callback");
  console.timeEnd("setTimeout");
}, 100);

let i = 0;
while (i < 1000000000) {
  i++;
}

Результат: setTimeout: 2103ms вместо 100ms!

Почему? Event Loop не может обработать колбэк из setTimeout, пока не закончится синхронный код. JavaScript сначала выполняет весь синхронный код, затем переходит к очередям.

Event Loop Phases в Node.js

timers (setTimeout, setInterval)
  ↓
pending callbacks
  ↓
poll (I/O events)
  ↓
check (setImmediate)
  ↓
close callbacks

setTimeout колбэки попадают в queue таймеров, но выполняются только когда Event Loop дойдёт до фазы timers и нет более приоритетных задач.

Пример: I/O операции блокируют setTimeout

const fs = require("fs");

setTimeout(() => {
  console.log("setTimeout: 100ms");
}, 100);

fs.readFile("/large-file.txt", (err, data) => {
  console.log("File read");
  // долгое CPU-bound вычисление
  let sum = 0;
  for (let i = 0; i < 10000000000; i++) {
    sum += i;
  }
});

Порядок:

  1. setTimeout планируется на 100ms
  2. fs.readFile запускается
  3. File read завершается на 50ms
  4. Начинается heavy computation на 5 сек
  5. setTimeout выполнится на 5+ сек!

Микротаски vs Макротаски

console.log("Start");

setTimeout(() => {
  console.log("setTimeout 0");
}, 0);

Promise.resolve()
  .then(() => console.log("Promise 1"))
  .then(() => console.log("Promise 2"));

console.log("End");

Результат:

Start
End
Promise 1
Promise 2
setTimeout 0

Promise (микротаски) имеют более высокий приоритет, чем setTimeout (макротаски).

Минимальное Значение Delay

По стандарту, минимальная задержка для setTimeout4ms (в Node.js ~1ms), даже если указано 0.

Решение 1: setImmediate

setImmediate(() => {
  console.log("Immediate callback");
});

setTimeout(() => {
  console.log("Timeout callback");
}, 0);

Результат: Immediate выполнится первым (фаза check раньше timers).

Решение 2: Worker Threads для CPU-bound задач

const { Worker } = require("worker_threads");

setTimeout(() => {
  console.log("Main thread timeout");
}, 100);

const worker = new Worker("./heavy-task.js");

Это не блокирует Event Loop.

Заключение

Функция в setTimeout может выполниться позже:

  1. Синхронный код блокирует Event Loop
  2. I/O операции потребляют время
  3. Микротаски (Promise) выполняются перед макротасками
  4. CPU-bound операции блокируют очередь

setTimeout — это не точный таймер, а инструмент для планирования задач. Для критичных по времени операций нужны Worker Threads или другие специализированные подходы.