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

Выполнится первее Promise.resolve() или setTimeout с нулевой задержкой

2.2 Middle🔥 272 комментариев
#JavaScript Core

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

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

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

Promise.resolve() vs setTimeout(0): Порядок выполнения

Короткий ответ: Promise.resolve() выполнится ПЕРВЫМ, потому что это микротаск, а setTimeout это макротаск. Микротаски ВСЕГДА выполняются перед макротасками.

Основной пример

Promise.resolve().then(() => {
  console.log('Promise.resolve() выполнился ПЕРВЫМ');
});

setTimeout(() => {
  console.log('setTimeout выполнился ВТОРЫМ');
}, 0);

console.log('Синхронный код');

// ВЫВОД:
// Синхронный код
// Promise.resolve() выполнился ПЕРВЫМ
// setTimeout выполнился ВТОРЫМ

Почему именно в таком порядке?

Это связано с Event Loop в JavaScript. Вот схема:

┌──────────────────────────────────┐
│  1. CALL STACK (синхронный код)  │
│  ├─ console.log('Синхронный..') │
│  └─ [выполнение завершено]       │
├──────────────────────────────────┤
│  2. MICROTASK QUEUE (очередь)    │
│  ├─ Promise.resolve().then()     │
│  └─ [выполняем это]              │
├──────────────────────────────────┤
│  3. MACROTASK QUEUE (очередь)    │
│  ├─ setTimeout(callback, 0)      │
│  └─ [выполняем это]              │
└──────────────────────────────────┘

Очередь микротасков ВСЕГДА выполняется полностью ПЕРЕД очередью макротасков.

Детальное объяснение: Как работает Event Loop

// Фаза 1: Синхронный код
console.log('START');

// Фаза 2: Микротаск (Promise)
Promise.resolve()
  .then(() => console.log('Promise 1'));
  .then(() => console.log('Promise 2'));

// Фаза 3: Макротаск (setTimeout)
setTimeout(() => console.log('setTimeout 1'), 0);

// Фаза 4: Еще микротаск
queueMicrotask(() => console.log('queueMicrotask'));

// Фаза 5: Еще макротаск
setTimeout(() => console.log('setTimeout 2'), 0);

console.log('END');

// ════════════════════════════════════════
// ПОРЯДОК ВЫПОЛНЕНИЯ:
// ════════════════════════════════════════

// ✓ Фаза 1: Синхронный код выполнен
// START
// END

// ✓ Фаза 2: Все микротаски (очередь)
// Promise 1
// Promise 2
// queueMicrotask

// ✓ Фаза 3: Первый макротаск
// setTimeout 1

// ✓ Фаза 4: Микротаски после первого макротаска (если есть)
// (микротасков нет)

// ✓ Фаза 5: Второй макротаск
// setTimeout 2

Множественные Promise vs Множественные setTimeout

// Promise.resolve() 1
Promise.resolve().then(() => console.log('Promise 1'));

// setTimeout 1
setTimeout(() => console.log('setTimeout 1'), 0);

// Promise.resolve() 2
Promise.resolve().then(() => console.log('Promise 2'));

// setTimeout 2
setTimeout(() => console.log('setTimeout 2'), 0);

// ВЫВОД:
// Promise 1      <- оба Promise выполняются ПЕРЕД setTimeout
// Promise 2
// setTimeout 1   <- потом оба setTimeout
// setTimeout 2

Микротаски vs Макротаски: Полный список

МИКРОТАСКИ (выполняются в ПЕРВУЮ очередь)

// 1. Promise.resolve(), Promise.reject()
Promise.resolve().then(() => {});
Promise.reject().catch(() => {});

// 2. queueMicrotask
queueMicrotask(() => {});

// 3. MutationObserver
new MutationObserver(() => {}).observe(document.body, {});

// 4. process.nextTick (Node.js)
process.nextTick(() => {});

// 5. async/await (это Promise под капотом)
async function test() {
  await promise; // это микротаск
}

МАКРОТАСКИ (выполняются ВО ВТОРУЮ очередь)

// 1. setTimeout
setTimeout(() => {}, 0);

// 2. setInterval
setInterval(() => {}, 0);

// 3. setImmediate (Node.js, не браузер)
setImmediate(() => {});

// 4. requestAnimationFrame (полу-макротаск)
requestAnimationFrame(() => {});

// 5. fetch, XMLHttpRequest (события load/error)
fetch('/api').then(...); // fetch это Promise (микротаск)

Парадокс: setTimeout(0)

Важно понимать, что setTimeout(0) не значит "выполнить сразу":

console.log('START');

setTimeout(() => {
  console.log('setTimeout выполнится позже');
}, 0); // Даже с нулевой задержкой!

console.log('END');

// ВЫВОД:
// START
// END
// setTimeout выполнится позже

// setTimeout(0) это минимум 4ms в браузере!
// Браузер может замедлить setTimeout если вкладка не в фокусе

Реальный пример из практики

// Загрузка данных с API
async function fetchData() {
  console.log('1. Начало загрузки');
  
  const response = await fetch('/api/data'); // Микротаск
  console.log('2. Данные получены');
  
  const data = await response.json(); // Еще микротаск
  console.log('3. JSON распарсен');
  
  // Отложить обновку на следующий frame
  setTimeout(() => {
    console.log('4. Обновление DOM (макротаск)');
    updateDOM(data);
  }, 0);
}

Promise.resolve().then(() => {
  console.log('5. Еще один Promise');
});

fetchData();

// ВЫВОД:
// 1. Начало загрузки
// 2. Данные получены
// 3. JSON распарсен
// 5. Еще один Promise
// 4. Обновление DOM (макротаск)

Проблема: неправильный порядок ожиданий

// ❌ НЕПРАВИЛЬНО: ожидаем что setTimeout выполнится первым
let data = null;

setTimeout(() => {
  data = 'loaded'; // Это выполнится вторым!
}, 0);

Promise.resolve().then(() => {
  console.log(data); // undefined (первый, до setTimeout)
});

// ✅ ПРАВИЛЬНО: понимаем что Promise выполнится первым
let data = null;

setTimeout(() => {
  data = 'loaded';
}, 0);

Promise.resolve().then(() => {
  setTimeout(() => {
    console.log(data); // 'loaded' (теперь верно)
  }, 0);
});

Практический совет для интервью

// Когда вас спрашивают "Что выполнится первым?"

// Запомните порядок:
// 1. СИНХРОННЫЙ КОД
// 2. МИКРОТАСКИ (Promise.resolve, queueMicrotask)
// 3. МАКРОТАСКИ (setTimeout, setInterval)

// Promise.resolve() это МИКРОТАСК
// setTimeout(0) это МАКРОТАСК
// Микротаски выполняются ПЕРЕД макротасками

// Поэтому:
Promise.resolve().then(() => console.log('ПЕРВЫЙ'));
setTimeout(() => console.log('ВТОРОЙ'), 0);
// ВЫВОД: ПЕРВЫЙ, ВТОРОЙ

Сложный пример для полного понимания

console.log('START');

// setTimeout - макротаск
setTimeout(() => {
  console.log('setTimeout 1');
  
  // Микротаск внутри макротаска
  Promise.resolve().then(() => {
    console.log('Promise внутри setTimeout');
  });
  
  // Еще макротаск внутри макротаска
  setTimeout(() => {
    console.log('setTimeout 2 внутри setTimeout 1');
  }, 0);
}, 0);

// Promise - микротаск
Promise.resolve().then(() => {
  console.log('Promise 1');
  
  // Макротаск внутри Promise
  setTimeout(() => {
    console.log('setTimeout 3 внутри Promise');
  }, 0);
}).then(() => {
  console.log('Promise 2');
});

// queueMicrotask - микротаск
queueMicrotask(() => {
  console.log('queueMicrotask');
});

console.log('END');

// ПОРЯДОК ВЫПОЛНЕНИЯ:
// START
// END
// ├─ [Все микротаски]
// ├─ Promise 1
// ├─ Promise 2
// ├─ queueMicrotask
// │
// ├─ [Первый макротаск]
// ├─ setTimeout 1
// ├─ [Микротаски после первого макротаска]
// ├─ Promise внутри setTimeout
// │
// ├─ [Второй макротаск]
// ├─ setTimeout 3 внутри Promise
// │
// ├─ [Третий макротаск]
// └─ setTimeout 2 внутри setTimeout 1

Итоговый ответ для интервью

Promise.resolve() выполнится ПЕРВЫМ.

Это потому что Promise.resolve().then() это микротаск, а setTimeout это макротаск. Event Loop в JavaScript ВСЕГДА выполняет все микротаски перед тем как перейти к макротаскам.

Порядок в Event Loop:

  1. Синхронный код
  2. ВСЕ микротаски (Promise, queueMicrotask)
  3. Первый макротаск (setTimeout)
  4. ВСЕ микротаски между макротасками
  5. Следующий макротаск

Даже setTimeout(0) с нулевой задержкой будет выполнен ПОСЛЕ Promise.resolve().

Выполнится первее Promise.resolve() или setTimeout с нулевой задержкой | PrepBro