Первая фаза - таймеры. Данная фаза выполняется непосредственно Event Loop'ом. Обрати внимание на фрагмент кода с uv_update_time на слайде 9 - здесь просто обновляется время, когда начал работать Event Loop. uv_run_timers - в этом методе выполняется следующее действие с таймером. Есть определённый стек, точнее, куча таймеров, - это, по сути, то же самое, что очередь, где находятся таймеры.
Берётся таймер с самым маленьким временем, сравнивается с текущим временем Event Loop'а, и, если настало время для исполнения данного таймера, выполняется его callback. Здесь стоит отметить, что в Node.js есть реализация setTimeout и есть setInterval. Для libuv это, по сути, одно и то же, только в setInterval ещё есть флаг repeat. Соответственно, если у данного таймера стоит флаг repeat, то он снова помещается в очередь событий и потом точно так же обрабатывается. Вторая фаза - I/O-callback'и.
Здесь надо вернуться к диаграмме про неблокирующий ввод/вывод. Когда демультиплексор событий выполняет чтение какого-либо файла и ставит выполнение callback'а в очередь, это как раз соответствует этапу I/O-callback. Здесь выполняются callback'и для неблокирующего ввода/вывода, т. е. это именно те функции, которые используются после запроса в базу данных или другой ресурс или на чтение/запись файла. Они выполняются именно на данной фазе.
На слайде 9 выполнение функции I/O-callback'и запускает строка 367: ran_pending = uv_run_pending(loop). Третья фаза - ожидание, подготовка. Это внутренние операции для callback'ов, по сути, мы не можем влиять на фазу, только косвенно. Есть process.nextTick, его callback может ненамеренно быть исполнен на фазе «ожидание, подготовка». process.nextTick выполняется на текущей фазе, т. е., по сути, process.nextTick может сработать абсолютно на любой фазе.
Какого-то готового инструмента, чтобы запустить код на фазе «ожидание, подготовка», в Node.js нет. На слайде 9 этой фазе соответствуют строки 368, 369: uv_run_idle(loop) - ожидание; uv_run_prepare(loop) - подготовка. Четвёртая фаза - опрос. Здесь выполняется весь наш код, который мы пишем на JS. Первоначально все запросы, которые мы делаем, попадают именно сюда, и именно здесь Node.js может быть заблокирована.
Если сюда попадёт какая-либо тяжёлая операция по вычислению, то на этом этапе наше приложение может просто зависнуть и ожидать, пока не выполнится данная операция. На слайде 9 функция опроса содержится в строке 370: uv_io_poll(loop, timeout). Пятая фаза - проверка. В Node.js есть таймер setImmediate, его callback'и выполняются на этой фазе. В исходном коде это строка 371: uv_run_check(loop). Шестая фаза (последняя) - callback'и событий close.
Например, web-сокету нужно закрыть соединение, на этой фазе будет вызван callback этого события. В исходном коде это строка 372: uv_run_closing_handless(loop). И в итоге Event Loop Node.js выглядит следующим образом. Слайд №
Сначала в очереди таймеров выполняется тот таймер, срок которого подошёл. Дальше выполняются I/O-callback'и. Затем - основной код, дальше - setImmediate и события close. После этого всё повторяется по кругу. Чтобы продемонстрировать это, открою код. Как он будет выполняться? У нас нет таймеров в очереди, поэтому Event Loop двигается дальше. I/O-callback'ов тоже нет, поэтому идём сразу на фазу опроса. Весь код, который здесь есть, изначально выполняется на фазе опроса.
Поэтому сначала печатаем script_start, setInterval у нас помещается в очередь таймеров (не выполняется, просто помещается). setTimeout также помещается в очередь таймеров, и затем выполняются промисы: сначала promise 1 и затем promise