Почему функция в setTimeout может вызваться позже чем указано время задержки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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;
}
});
Порядок:
- setTimeout планируется на 100ms
- fs.readFile запускается
- File read завершается на 50ms
- Начинается heavy computation на 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
По стандарту, минимальная задержка для setTimeout — 4ms (в 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 может выполниться позже:
- Синхронный код блокирует Event Loop
- I/O операции потребляют время
- Микротаски (Promise) выполняются перед макротасками
- CPU-bound операции блокируют очередь
setTimeout — это не точный таймер, а инструмент для планирования задач. Для критичных по времени операций нужны Worker Threads или другие специализированные подходы.