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

Listener - это микро или макротаск

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

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

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

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

Event Listeners: Микротаски или макротаски?

Коротко: Event Listeners (обработчики событий) это ни микротаски, ни макротаски сами по себе. Они запускаются между макротасками. Однако, то что находится внутри listener-а может содержать микротаски и макротаски.

Порядок выполнения в JavaScript Event Loop

Давайте разберемся с полной картиной Event Loop:

┌─────────────────────────────────────────┐
│  1. Выполнение синхронного кода         │
├─────────────────────────────────────────┤
│  2. Все микротаски                      │
│     (Promise.then, queueMicrotask)      │
├─────────────────────────────────────────┤
│  3. Первый макротаск (setTimeout, etc)  │
├─────────────────────────────────────────┤
│  4. ВСЕ микротаски между макротасками   │
├─────────────────────────────────────────┤
│  5. Следующий макротаск                 │
├─────────────────────────────────────────┤
│  6. Все микротаски                      │
└─────────────────────────────────────────┘

Где находятся Event Listeners в этом порядке?

Event Listeners запускаются вместе с синхронным кодом, но между макротасками:

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

// Слушатель события
document.addEventListener('click', () => {
  console.log('4. Event Listener (при клике)');
});

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

setTimeout(() => {
  console.log('3. Макротаск (setTimeout)');
}, 0);

console.log('1. Больше синхронного кода');

// Вывод при нажатии кнопки:
// 1. Синхронный код
// 1. Больше синхронного кода
// 2. Микротаск (Promise)
// [клик мышкой]
// 4. Event Listener
// 3. Макротаск (setTimeout)

Важное уточнение: когда именно запускаются обработчики

Обработчики событий запускаются:

  1. Синхронно когда происходит событие (например, клик)
  2. Между макротасками в Event Loop
  3. ДО выполнения микротасков следующего макротаска
// Более точный пример
console.log('Start');

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

// Event Listener
document.addEventListener('click', () => {
  console.log('Event Listener (при клике)');
  
  // Микротаск внутри listener-а
  Promise.resolve().then(() => {
    console.log('Микротаск внутри listener-а');
  });
});

// Микротаск в глобальной области
Promise.resolve().then(() => {
  console.log('Глобальный микротаск');
});

console.log('End');

// Без клика - вывод:
// Start
// End
// Глобальный микротаск
// Макротаск 1: setTimeout
// Микротаск внутри макротаска

// [КЛИК]
// Event Listener (при клике)
// Микротаск внутри listener-а
// Макротаск 1: setTimeout
// Микротаск внутри макротаска

Детальный разбор Event Loop с Listeners

Вот подробный порядок выполнения:

console.log('Script start');

// 1. Макротаск
setTimeout(() => {
  console.log('Макротаск: setTimeout 1');
  
  // Микротаск внутри макротаска
  Promise.resolve().then(() => {
    console.log('  - Микротаск: promise в setTimeout');
  });
  
  // Еще один listener event
  document.addEventListener('click', () => {
    console.log('    - Event Listener в setTimeout');
  });
}, 0);

// 2. Event Listener (регистрация)
document.addEventListener('click', () => {
  console.log('Event Listener: основной');
  
  Promise.resolve().then(() => {
    console.log('  - Микротаск в listener-е');
  });
});

// 3. Микротаск
Promise.resolve()
  .then(() => {
    console.log('Микротаск: promise 1');
    return 'value';
  })
  .then(() => {
    console.log('Микротаск: promise 2');
  });

// 4. Еще один макротаск
setTimeout(() => {
  console.log('Макротаск: setTimeout 2');
}, 0);

console.log('Script end');

// ВЫВОД БЕЗ КЛИКА:
// 1. Script start
// 2. Script end
// 3. Микротаск: promise 1
// 4. Микротаск: promise 2
// 5. Макротаск: setTimeout 1
// 6.   - Микротаск: promise в setTimeout
// 7. Макротаск: setTimeout 2

// ===== [ПОЛЬЗОВАТЕЛЬ КЛИКАЕТ] =====
// 8. Event Listener: основной
// 9.   - Микротаск в listener-е
// 10. Макротаск: setTimeout 1
// 11.   - Микротаск: promise в setTimeout
// 12. Макротаск: setTimeout 2

Типы микротасков и макротасков

Микротаски (Microtasks)

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

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

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

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

Макротаски (Macrotasks)

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

// 2. setImmediate (Node.js, не стандарт для браузера)
setImmediate(() => {});

// 3. I/O операции (Node.js)
// 4. UI rendering
// 5. fetch, XMLHttpRequest (частично макротаск)
// 6. requestAnimationFrame (не совсем макротаск)

Event Listeners и микротаски внутри

Самый важный момент: то что внутри listener-а может содержать микротаски:

document.addEventListener('click', async () => {
  console.log('1. Event Listener start');
  
  // Микротаск внутри listener-а (await это Promise)
  await fetch('/api/data');
  console.log('2. После fetch (микротаск)');
  
  // Еще микротаск
  await somePromise();
  console.log('3. После promise');
  
  // Макротаск внутри listener-а
  setTimeout(() => {
    console.log('4. setTimeout внутри listener-а');
  }, 0);
});

// При клике:
// 1. Event Listener start
// 2. После fetch
// 3. После promise
// [остальной код выполнится]
// 4. setTimeout внутри listener-а

Практический пример: Порядок выполнения

console.log('1. Start');

// setTimeout - макротаск
setTimeout(() => {
  console.log('6. setTimeout (макротаск)');
}, 0);

// Event Listener - запускается между макротасками
button.addEventListener('click', () => {
  console.log('7. Click handler');
  Promise.resolve().then(() => {
    console.log('8. Promise в click handler');
  });
});

// Promise - микротаск
Promise.resolve().then(() => {
  console.log('3. Promise 1 (микротаск)');
  return Promise.resolve();
}).then(() => {
  console.log('4. Promise 2 (микротаск)');
});

// Еще setTimeout
setTimeout(() => {
  console.log('9. setTimeout 2 (макротаск)');
}, 0);

console.log('2. End');

// Вывод (без клика):
// 1. Start
// 2. End
// 3. Promise 1 (микротаск)
// 4. Promise 2 (микротаск)
// 6. setTimeout (макротаск)
// 9. setTimeout 2 (макротаск)

// [ПОЛЬЗОВАТЕЛЬ КЛИКАЕТ НА КНОПКУ]
// 7. Click handler
// 8. Promise в click handler

Важные моменты для интервью

1. Event Listeners это НЕ макротаски и НЕ микротаски

// Listener запускается независимо от Event Loop
// Он просто запускается когда происходит событие

document.addEventListener('click', () => {
  // Запускается синхронно при клике
  // Но ПОСЛЕ текущего стека вызовов
});

2. Event Listener выполняется ДО микротасков после себя

button.addEventListener('click', () => {
  console.log('1. Event Listener');
  Promise.resolve().then(() => {
    console.log('2. Микротаск');
  });
});

// При клике:
// 1. Event Listener
// 2. Микротаск

3. Микротаски внутри listener-а выполняются сразу

document.addEventListener('click', async () => {
  console.log('1. Start');
  await fetch('/api'); // Это микротаск
  console.log('2. End'); // Выполнится сразу после Promise
});

setTimeout(() => {
  console.log('3. setTimeout'); // Выполнится после всех микротасков в listener
}, 0);

// При клике:
// 1. Start
// 2. End
// 3. setTimeout

Ответ на интервью

Правильный ответ:

Event Listener это ни микротаск, ни макротаск. Listener запускается синхронно при возникновении события и выполняется между выполнением макротасков.

Однако, то что находится внутри listener-а может содержать микротаски (Promise) или макротаски (setTimeout). Все микротаски внутри listener-а выполняются в одном цикле Event Loop перед следующим макротаском.

// Порядок:
// 1. Синхронный код
// 2. Микротаски (Promise)
// [СОБЫТИЕ: клик]
// 3. Event Listener
// 4. Микротаски внутри listener-а
// 5. Следующий макротаск (setTimeout)
Listener - это микро или макротаск | PrepBro