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

Когда у setTimeout вместо задержки 0 появляется другое значение?

2.3 Middle🔥 191 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Когда у setTimeout с задержкой 0 появляется другое значение

Это глубокий вопрос о том, как работает Event Loop в JavaScript. Задержка в 0 миллисекунд не означает, что код выполнится мгновенно - на самом деле может пройти гораздо больше времени.

Как работает Event Loop

JavaScript является однопоточным языком, но браузер использует Event Loop для управления выполнением кода:

// Порядок выполнения
console.log('1. Синхронный код');

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

Promise.resolve()
  .then(() => {
    console.log('3. Promise (микротаск)');
  });

console.log('2. Ещё синхронный код');

// Вывод:
// 1. Синхронный код
// 2. Ещё синхронный код
// 3. Promise (микротаск)
// 4. setTimeout 0

Стеки и очереди:

  1. Call Stack - синхронный код
  2. Microtask Queue - Promises, MutationObserver
  3. Macrotask Queue - setTimeout, setInterval, setImmediate

Event Loop обрабатывает их так:

  1. Выполняет весь синхронный код из Call Stack
  2. Выполняет все микротаски из Microtask Queue
  3. Выполняет один макротаск из Macrotask Queue
  4. Снова выполняет все микротаски
  5. И так далее...

Минимальная задержка setTimeout

У setTimeout есть минимальная задержка 4 миллисекунды согласно спецификации HTML5. Даже если указать 0, браузер будет ждать минимум 4ms:

const start = performance.now();

setTimeout(() => {
  const end = performance.now();
  console.log(`Прошло: ${end - start}ms`); // Выведет примерно 4-5ms, не 0
}, 0);

Когда появляется другое значение

1. Глубокая вложенность setTimeout

Если setTimeout вложен в другой setTimeout глубже, чем на 4-5 уровней, браузер увеличивает минимальную задержку:

// Уровень 1
setTimeout(() => {
  // Уровень 2
  setTimeout(() => {
    // Уровень 3
    setTimeout(() => {
      // Уровень 4
      setTimeout(() => {
        // Уровень 5
        setTimeout(() => {
          // Уровень 6 - здесь задержка может быть больше, например 16ms
          console.log('Выполнено');
        }, 0);
      }, 0);
    }, 0);
  }, 0);
}, 0);

Спецификация говорит: если нет активного таймера и глубина больше 5, минимальная задержка должна быть 1000ms, но браузеры обычно используют 4ms до уровня 5, потом увеличивают.

2. Спешивший CPU (Throttled CPU)

Когда браузер вкладка находится в фоне (не активна), браузер может увеличить минимальную задержку для экономии ресурсов:

// В активной вкладке
setTimeout(() => console.log('4-5ms'), 0);

// В фоновой вкладке (user переключился на другую вкладку)
setTimeout(() => console.log('1000ms или больше'), 0);

3. Блокировка от события (Event throttling)

Если код запущен из обработчика события (например, onClick), браузер может соблюдать другие правила:

// Обработчик события
button.addEventListener('click', () => {
  // Здесь браузер может применить другие ограничения
  setTimeout(() => {
    console.log('Задержка может быть другой');
  }, 0);
});

На практике: когда я это встречал

Проблема 1: Обновление DOM

При попытке получить новое значение DOM сразу после изменения:

const element = document.getElementById('content');
element.style.opacity = '0';

// Нужно дождаться, пока браузер перерисует (reflow)
setTimeout(() => {
  element.style.opacity = '1'; // Плавная анимация
}, 0);

// Но на самом деле нужно использовать requestAnimationFrame
requestAnimationFrame(() => {
  element.style.opacity = '1';
});

Проблема 2: Race condition при загрузке ресурсов

const img = new Image();
img.onload = () => {
  console.log('Картинка загружена');
  
  setTimeout(() => {
    // Может быть поздновато, если сеть быстрая
    console.log('Выполняется после картинки');
  }, 0);
};

img.src = 'large.jpg';

Проблема 3: Измерение производительности

const start = performance.now();

setTimeout(() => {
  const duration = performance.now() - start;
  console.log(`Настоящая задержка: ${duration}ms`); // Не 0!
}, 0);

Как я решаю эти проблемы

1. Используй requestAnimationFrame вместо setTimeout для визуальных обновлений:

// Плохо: непредсказуемая задержка
setTimeout(() => {
  element.style.transform = 'translateX(100px)';
}, 0);

// Хорошо: синхронизировано с frame rate браузера
requestAnimationFrame(() => {
  element.style.transform = 'translateX(100px)';
});

2. Используй Promises для асинхронности вместо setTimeout(0):

// setTimeout 0 с задержкой
setTimeout(() => {
  processData();
}, 0);

// Promise - выполнится сразу после микротасков (быстрее)
Promise.resolve().then(() => {
  processData();
});

3. Для обновления DOM используй MutationObserver или batch updates:

// Плохо: множество setTimeout
for (let i = 0; i < 100; i++) {
  setTimeout(() => {
    updateElement(i);
  }, 0);
}

// Хорошо: батч обновление
const updates = [];
for (let i = 0; i < 100; i++) {
  updates.push(i);
}

setTimeout(() => {
  updates.forEach(updateElement);
}, 0);

4. Не полагайся на setTimeout(0) для критических операций:

// Плохо: непредсказуемо
function ensureDataLoaded() {
  if (!dataLoaded) {
    setTimeout(ensureDataLoaded, 0);
  }
}

// Хорошо: используй Promises или async/await
async function ensureDataLoaded() {
  if (!dataLoaded) {
    await loadData();
  }
}

Практический тест

const testSetTimeout = () => {
  const results = [];
  
  for (let depth = 0; depth <= 6; depth++) {
    const start = performance.now();
    
    let cb = () => {
      const duration = performance.now() - start;
      results.push(`Depth ${depth}: ${duration.toFixed(2)}ms`);
    };
    
    // Создаём вложенность
    let currentCb = cb;
    for (let i = 0; i < depth; i++) {
      currentCb = (() => {
        const nextCb = currentCb;
        return () => setTimeout(nextCb, 0);
      })();
    }
    
    setTimeout(currentCb, 0);
  }
  
  setTimeout(() => {
    results.forEach(r => console.log(r));
  }, 100);
};

Вывод

setTimeout с задержкой 0 - это не 0 миллисекунд, а скорее "как можно скорее" с различными ограничениями:

  • Минимум 4-5ms для стандартного случая
  • Больше при глубокой вложенности (уровень 5+)
  • Больше в фоновых вкладках (1000ms+)
  • Зависит от браузера и состояния системы

Для предсказуемого поведения лучше использовать requestAnimationFrame для визуальных изменений и Promises для асинхронности.

Когда у setTimeout вместо задержки 0 появляется другое значение? | PrepBro