Загрузка…
Загрузка…
Node.js · middle · сложность 5
Цикл событий Node.js — это фундаментальная концепция, которая позволяет Node.js выполнять неблокирующие операции ввода-вывода, несмотря на то, что он является однопоточным. По сути, это непрерывный цикл, который проверяет, есть ли работа, и если да, то обрабатывает ее.
Представьте себе, что цикл событий — это официант ресторана (Node.js), который может одновременно обслуживать только одного клиента (задачу), но может быстро переключаться между клиентами. Когда клиент заказывает еду (например, запрашивает данные из базы данных), официант не просто стоит и ждет, пока еда приготовится. Вместо этого они доставляют заказ на кухню (делегируют в фоновый пул потоков C++), идут обслуживать других клиентов, убирают столы или принимают новые заказы. Как только кухня сигнализирует, что еда готова (операция ввода-вывода завершена), официант доставляет ее покупателю. Этот непрерывный процесс проверки, обслуживания и делегирования называется циклом событий.
Цикл событий работает в отдельные фазы, каждая из которых имеет свою собственную очередь обратных вызовов FIFO (первым пришел, первым вышел). Когда цикл событий начинает новую итерацию, он обрабатывает все обратные вызовы в очереди текущей фазы, прежде чем перейти к следующей фазе.
Вот основные этапы по порядку:
timers
**: * Цель: на этом этапе выполняются обратные вызовы, запланированные
setTimeout()
и
setInterval()
. * Как это работает: проверяет, истекла ли указанная задержка для таймеров. Если порог таймера пройден, выполняется его обратный вызов. * Аналогия: Официант проверяет, готовы ли какие-либо заказы на еду, сделанные ранее (например, 5 минут назад), к тому, чтобы их можно было забрать с кухни.
pending callbacks
**: * Цель: выполняет обратные вызовы ввода-вывода, которые были отложены до следующей итерации цикла. Сюда могут относиться некоторые системные ошибки (например, ошибка соединения из-за операций TCP). * Как это работает: обрабатывает обратные вызовы для системных операций, которые могут не иметь четкого сигнала «готовность», как обычный ввод-вывод. * Аналогия: официант работает с особой жалобой клиента, которую невозможно решить немедленно и которая требует дальнейших действий.
idle, prepare
**: * Цель: внутренняя для Node.js. * Как это работает: Используется только для внутреннего использования. * Аналогия: официант делает небольшой перерыв или готовит поднос перед следующим важным заданием.
poll
**: * Цель: это самый важный этап. Он извлекает новые события ввода-вывода и выполняет обратные вызовы, связанные с вводом-выводом (например, чтение файлов, сетевые запросы, запросы к базе данных). * Как это работает: * Если имеются ожидающие таймеры и сценарий бездействует, он может подождать в течение короткого периода времени, чтобы позволить поступить новым событиям ввода-вывода, а затем выполнить их обратные вызовы. * Если ожидающих событий ввода-вывода нет, цикл событий может заблокироваться и ждать новых подключений, данных и т. д. * Как только очередь опустеет или будет выполнено максимальное количество обратных вызовов, цикл переходит к
check
фаза. * Аналогия: теперь официант активно проверяет кухонную стойку на наличие новых заказов на еду или ищет новых клиентов, заходящих в ресторан. Если еда не готова и нет новых клиентов, они могут просто подождать у двери.
check
**: * Цель: Выполнение
setImmediate()
обратные вызовы. * Как это работает: Если
poll
фаза становится бездействующей (т. е. больше нет обратных вызовов ввода-вывода для обработки или истекло время ожидания новых), цикл событий немедленно переходит к этой фазе для выполнения
setImmediate
обратные вызовы. * Аналогия: Официант проверяет специальный список «срочных задач» после выполнения основной работы по обслуживанию клиентов (ввод-вывод).
close callbacks
**: * Цель: выполняет обратные вызовы для
close
события, например, когда сокет или дескриптор закрываются. * Как это работает. Например,
socket.on('close', ...)
. * Аналогия: Официант убирает со столов и готовится к закрытию, выполняет заключительную уборку.
Микрозадачи (вне основных этапов): Прежде чем перейти от одной фазы к другой и после первоначального выполнения текущего скрипта, Node.js обрабатывает очередь «микрозадач». К ним относятся обратные вызовы от
process.nextTick()
и обещания (
.then()
,
catch()
,
finally()
,
await
). Это означает, что микрозадачи имеют более высокий приоритет и запускаются сразу после завершения макрозадач текущей операции или фазы, до перехода цикла событий к следующей фазе.
process.nextTick
,
setImmediate
, и
setTimeout(fn, 0)
Давайте посмотрим, как эти три специальные функции взаимодействуют с этапами цикла событий и их типичными вариантами использования.
process.nextTick(callback)
process.nextTick
обратные вызовы НЕ являются частью фаз цикла событий. Они помещаются в очередь микрозадач, которая обрабатывается сразу после завершения текущей операции, но до того, как цикл событий перейдет к следующему этапу или даже начнет новую итерацию этапов. По сути, они «без очереди».
setTimeout
или
setImmediate
вызовы, если они запланированы в том же синхронном блоке.
console.log('1. Start of script');
process.nextTick(() => {
console.log('2. process.nextTick callback (microtask)');
});
console.log('3. End of script');
// Output:
// 1. Start of script
// 3. End of script
// 2. process.nextTick callback (microtask)
```
*Объяснение*: Сначала выполняется синхронный код, затем
`process.nextTick`
запускается, потому что его очередь микрозадач обрабатывается *до* того, как цикл событий начинает свою первую фазу.
#### 2.
`setTimeout(callback, 0)`
## Итог
Ключевой вывод для интервью: опишите этапы цикла событий Node.js. Как взаимодействовать с `process.nextTick`, `setImmediate` и `setTimeout(fn, 0)`