The first phase is timers. This phase is executed directly by the Event Loop. Pay attention to the code fragment with uv_update_time on slide 9 - it simply updates the time when the Event Loop started running. uv_run_timers is the method that performs the next action for a timer. There is a certain stack, or rather a heap of timers, which is essentially the same as the queue where timers are stored.
The timer with the smallest delay is taken and compared with the current Event Loop time, and if it is time to execute that timer, its callback runs. It is worth noting that Node.js has both setTimeout and setInterval. For libuv, they are essentially the same thing, except setInterval also has a repeat flag. Accordingly, if that timer has the repeat flag set, it is placed back into the event queue and then processed in the same way again. The second phase is I/O callbacks.
Here we should go back to the non-blocking I/O diagram. When the event demultiplexer reads some file and queues a callback for execution, this corresponds exactly to the I/O callback stage. This is where callbacks for non-blocking I/O run — that is, precisely the functions used after a query to a database or another resource, or after a file read/write. They execute specifically in this phase.
On slide 9, execution of the I/O callbacks function is triggered by line 367: ran_pending = uv_run_pending(loop). The third phase is wait and prepare. These are internal operations for callbacks, and in practice we cannot influence the phase directly, only indirectly. There is process.nextTick, and its callback may unintentionally be executed in the wait and prepare phase. process.nextTick runs in the current phase, which means process.nextTick can effectively fire in absolutely any phase.
There is no ready-made tool in Node.js to run code in the wait and prepare phase. On slide 9, this phase corresponds to lines 368 and 369: uv_run_idle(loop) for waiting, and uv_run_prepare(loop) for preparation. The fourth phase is poll. This is where all the code we write in JS runs. Initially, all requests we make land here, and this is where Node.js can be blocked.
If a heavy computation lands here, the application may simply freeze at this stage and wait until that operation completes. On slide 9, the polling function is in line 370: uv_io_poll(loop, timeout). The fifth phase is check. In Node.js, there is a setImmediate timer, and its callbacks run in this phase. In the source code, this is line 371: uv_run_check(loop). The sixth phase, and the last one, is close event callbacks.
For example, a web socket needs to close a connection; in this phase the callback for that event will be invoked. In the source this is line 372: uv_run_closing_handless(loop). And in the end the Node.js Event Loop looks as follows. Slide No.
First, the timer whose deadline has arrived is executed in the timers queue. Next, I/O callbacks are executed. Then comes the main code, followed by setImmediate and close events. After that, everything repeats in a loop. To demonstrate this, I'll open the code. How will it execute? We have no timers in the queue, so the Event Loop moves on. There are no I/O callbacks either, so we go straight to the poll phase. All the code here initially executes in the poll phase.
So first we print script_start, setInterval is placed into the timers queue (not executed, just queued). setTimeout is also placed into the timers queue, and then the promises run: first promise 1 and then promise