Вопрос проверяет понимание асинхронной природы JavaScript и того, как промисы интегрируются в его внутренний механизм выполнения.
Промисы тесно связаны с Event Loop. Когда создается промис, его асинхронная операция (например, запрос к серверу) передается браузерному API и выполняется вне основного потока. Event Loop постоянно проверяет, завершилась ли эта операция. Когда она завершается, callback-функции промиса (.then(), .catch()) помещаются в очередь микрозадач (Microtask Queue), которая имеет высший приоритет. Event Loop выполнит все callback'и из этой очереди сразу после выполнения текущего синхронного кода, прежде чем перейти к чему-либо еще.
Связь промисов и Event Loop — фундаментальная концепция для понимания асинхронности в JavaScript.
1. Роль Event Loop
Event Loop — это механизм, который непрерывно проверяет, есть ли задачи для выполнения. Его работа заключается в том, чтобы брать задачи из очередей и помещать их в Call Stack (стек вызовов), когда тот пуст.
2. Жизненный цикл промиса
Создание и выполнение: Когда вы создаете новый промис и запускаете асинхронную операцию (например, fetch), эта операция передается из основного потока JavaScript в окружение браузера (Web API), где и выполняется.
Ожидание: Пока операция выполжается, основной поток JavaScript свободен и может делать другие вещи.
Завершение: Как только операция завершается (успешно или с ошибкой), callback-функции этого промиса (указанные в .then() или .catch()) не выполняются сразу. Вместо этого они помещаются в специальную очередь микрозадач (Microtask Queue).
3. Приоритет очереди микрозадач
После того как Call Stack полностью очистится (выполнится весь текущий синхронный код), Event Loop обращается к очередям.
Важное правило: Event Loop сначала выполняет все задачи в очереди микрозадач, и только потом переходит к очереди рендеринга или очереди макрозадач (например, setTimeout).
Это означает, что колбэки промисов имеют высокий приоритет и выполняются почти сразу после завершения асинхронной операции, не дожидаясь других событий.
Пример:
console.log('Start'); // 1. Синхронный код
setTimeout(() => console.log('Timeout'), 0); // 2. Макрозадача -> в очередь макрозадач
Promise.resolve()
.then(() => console.log('Promise')); // 3. Микрозадача -> в очередь микрозадач
console.log('End'); // 4. Синхронный код
// Output:
// "Start"
// "End"
// "Promise" -> выполнился первым, так как микрозадачи имеют приоритет
// "Timeout"