Перейти к содержанию

Event loop

Event loop (или цикл событий) позволяет выполнять однопоточному Node.js неблокирующие операции ввода/вывода, передавая их выполнение ядру системы, когда это возможно.

Стадии event loop

Кратко работу event loop Node.js можно описать так: операция передается на выполнения ядру системы, после завершения Node.js получает уведомление в том, что определенная для операции callback-функция может быть добавлена в очередь выполнения.

Инициализация event loop происходит в момент запуска сервера Node.js, и с этого момента он начинает свою работу, которую можно разделить на несколько этапов:

  • timers - выполнение callback-функций, зарегистрированных функциями setTimeout() и setInterval();
  • pending callbacks - вызов callback-функций операций ввода/вывода, выполнение которых было отложено на предыдущей стадии цикла событий;
  • idle, prepare - выполнение внутренних действий, необходимых самому event loop;
  • poll - выполнение callback-функций завершенных асинхронных операций и управление фазой timers;
  • check - выполнение callback-функций, зарегистрированных функцией setImmediate();
  • close callbacks - обработка внезапно завершающихся действий.

На стадии timers выполняются зарегистрированные таймерами функции, причем переход на стадию контролируется стадией poll. Из-за блокировки стадией poll цикла событий, таймеры могут выполняться с некоторой задержкой, т. е. через больший интервал времени, чем тот, который был задан. Рассмотрим ситуацию на примере.

let request = require('request')

setTimeout(() => console.log('Timeout'), 25)

//предполагаем, что запрос выполняется 20 миллисекунд, а callback - 10 миллисекунд
request('http://www.example.com', (error, response, body) => console.log('Response: ', response))

В примере сперва вызывается функция setTimeout(), которая должна выполнить переданную ей функцию через 25 миллисекунд. Затем сразу же делает запрос к удаленному API, занимающий 20 миллисекунд. В момент завершения запроса до вызова функции в таймере останется еще 5 миллисекунд, поэтому event loop начнет выполнять callback-функцию, определенную для обработки результата запроса, выполнение которой занимает 10 миллисекунд. Получается, что в предполагаемый момент времени выполнение таймера будет невозможным из-за занятости цикла событий и его вызов произойдет по окончанию работы callback-а запроса, а именно через 30 миллисекунд после определения самого таймера.

Результат работы кода.

Response: {response}
Timeout

При pending callbacks выполняются действия, отложенные на предыдущей итерации event loop. Например, это могут быть сообщения об ошибках, которые не были выведены ранее из-за попыткеи системы их исправить.

При переходе в фазу poll в первую очередь проверяется, сформировалась ли очередь из callback-функций выполненных асинхронных действий. Если очередь не пуста, то в синхронном порядке начинается выполнение всех функций, находящихся в очереди. Выполнение будет продолжаться до тех пор, пока очередь не опустеет или не будет достигнут лимит выполняемых за раз callback-функций.

Если очередь оказывается пустой, то проверяется наличие действий, заданных функцией setImmediate(), и если таковые имеются - происходит переход на стадию check, в противном случае Node.js event loop проверит, есть ли таймеры для выполнения. Если таймеры имеются - произойдет переход на timers, если нет - event loop будет ждать добавления в очередь новых callback-ов и при их появлении сразу же начинать их выполнение.

Для недопущения длительной блокировки event loop, в Node.js имеется ограничение на количество выполняемых на стадии poll callback-функций.

На стадии close callbacks вызываются функции, зарегистрированные для действий, возникающих внезапно. например, событие close или disconnect для сокет соединения.

process.nextTick()

Node.js process.nextTick() позволяет выполнять переданные ему callback-функции в текущий момент времени, вне зависимости от того, на какой стадии находится выполнение event loop. Выполнение самого event loop продолжится сразу после завершения всех переданных process.nextTick() callback-ов.

process.nextTick(() => console.log('After'))
console.log('Before')

Выполнение переданной process.nextTick() callback-функции начинается сразу после завершения текущей итерации event loop.