|
|
@ -9,7 +9,7 @@ offloading operations to the system kernel whenever possible. |
|
|
|
Since most modern kernels are multi-threaded, they can handle multiple |
|
|
|
operations executing in the background. When one of these operations |
|
|
|
completes, the kernel tells Node.js so that the appropriate callback |
|
|
|
may added to the `poll` queue to eventually be executed. We'll explain |
|
|
|
may added to the **poll** queue to eventually be executed. We'll explain |
|
|
|
this in further detail later in this topic. |
|
|
|
|
|
|
|
## Event Loop Explained |
|
|
@ -17,7 +17,7 @@ this in further detail later in this topic. |
|
|
|
When Node.js starts, it initializes the event loop, processes the |
|
|
|
provided input script (or drops into the REPL, which is not covered in |
|
|
|
this document) which may make async API calls, schedule timers, or call |
|
|
|
`process.nextTick()`, then begins processing the event loop. |
|
|
|
`process.nextTick()`, then begins processing the event loop. |
|
|
|
|
|
|
|
The following diagram shows a simplified overview of the event loop's |
|
|
|
order of operations. |
|
|
@ -47,36 +47,36 @@ Each phase has a FIFO queue of callbacks to execute. While each phase is |
|
|
|
special in its own way, generally, when the event loop enters a given |
|
|
|
phase, it will perform any operations specific to that phase, then |
|
|
|
execute callbacks in that phase's queue until the queue has been |
|
|
|
exhausted or the maximum number of callbacks have executed. When the |
|
|
|
exhausted or the maximum number of callbacks has executed. When the |
|
|
|
queue has been exhausted or the callback limit is reached, the event |
|
|
|
loop will move to the next phase, and so on. |
|
|
|
|
|
|
|
Since any of these operations may schedule _more_ operations and new |
|
|
|
events processed in the `poll` phase are queued by the kernel, poll |
|
|
|
events processed in the **poll** phase are queued by the kernel, poll |
|
|
|
events can be queued while polling events are being processed. As a |
|
|
|
result, long running callbacks can allow the poll phase to run much |
|
|
|
longer than a timer's threshold. See the [`timers`](#timers) and |
|
|
|
[`poll`](#poll) sections for more details. |
|
|
|
longer than a timer's threshold. See the [**timers**](#timers) and |
|
|
|
[**poll**](#poll) sections for more details. |
|
|
|
|
|
|
|
_**NOTE:** There is a slight discrepancy between the Windows and the |
|
|
|
Unix/Linux implementation, but that's not important for this |
|
|
|
demonstration. The most important parts are here. There are actually |
|
|
|
seven or eight steps, but the ones we care about — ones that Node.js |
|
|
|
actually uses are those above._ |
|
|
|
actually uses - are those above._ |
|
|
|
|
|
|
|
|
|
|
|
## Phases Overview: |
|
|
|
## Phases Overview |
|
|
|
|
|
|
|
* `timers`: this phase executes callbacks scheduled by `setTimeout()` |
|
|
|
* **timers**: this phase executes callbacks scheduled by `setTimeout()` |
|
|
|
and `setInterval()`. |
|
|
|
* `I/O callbacks`: most types of callback except timers, setImmedate, close |
|
|
|
* `idle, prepare`: only used internally |
|
|
|
* `poll`: retrieve new I/O events; node will block here when appropriate |
|
|
|
* `check`: setImmediate callbacks are invoked here |
|
|
|
* `close callbacks`: e.g socket.on('close', ...) |
|
|
|
* **I/O callbacks**: most types of callback except timers, `setImmedate()`, close |
|
|
|
* **idle, prepare**: only used internally |
|
|
|
* **poll**: retrieve new I/O events; node will block here when appropriate |
|
|
|
* **check**: `setImmediate()` callbacks are invoked here |
|
|
|
* **close callbacks**: e.g socket.on('close', ...) |
|
|
|
|
|
|
|
Between each run of the event loop, Node.js checks if it is waiting for |
|
|
|
any asynchronous I/O or timer and it shuts down cleanly if there are not |
|
|
|
any asynchronous I/O or timers and shuts down cleanly if there are not |
|
|
|
any. |
|
|
|
|
|
|
|
## Phases in Detail |
|
|
@ -90,7 +90,7 @@ scheduled after the specified amount of time has passed; however, |
|
|
|
Operating System scheduling or the running of other callbacks may delay |
|
|
|
them. |
|
|
|
|
|
|
|
_**Note**: Technically, the [`poll` phase](#poll) controls when timers |
|
|
|
_**Note**: Technically, the [**poll** phase](#poll) controls when timers |
|
|
|
are executed._ |
|
|
|
|
|
|
|
For example, say you schedule a timeout to execute after a 100 ms |
|
|
@ -102,10 +102,8 @@ takes 95 ms: |
|
|
|
var fs = require('fs'); |
|
|
|
|
|
|
|
function someAsyncOperation (callback) { |
|
|
|
|
|
|
|
// let's assume this takes 95ms to complete |
|
|
|
// Assume this takes 95ms to complete |
|
|
|
fs.readFile('/path/to/file', callback); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var timeoutScheduled = Date.now(); |
|
|
@ -131,78 +129,77 @@ someAsyncOperation(function () { |
|
|
|
}); |
|
|
|
``` |
|
|
|
|
|
|
|
When the event loop enters the `poll` phase, it has an empty queue |
|
|
|
(`fs.readFile()` has not completed) so it will wait for the number of ms |
|
|
|
When the event loop enters the **poll** phase, it has an empty queue |
|
|
|
(`fs.readFile()` has not completed), so it will wait for the number of ms |
|
|
|
remaining until the soonest timer's threshold is reached. While it is |
|
|
|
waiting 95 ms pass, `fs.readFile()` finishes reading the file and its |
|
|
|
callback which takes 10 ms to complete is added to the `poll` queue and |
|
|
|
callback which takes 10 ms to complete is added to the **poll** queue and |
|
|
|
executed. When the callback finishes, there are no more callbacks in the |
|
|
|
queue, so the event loop will see that the threshold of the soonest |
|
|
|
timer has been reached then wrap back to the `timers` phase to execute |
|
|
|
timer has been reached then wrap back to the **timers** phase to execute |
|
|
|
the timer's callback. In this example, you will see that the total delay |
|
|
|
between the timer being scheduled and its callback being executed will |
|
|
|
between the timer being scheduled and its callback being executed will |
|
|
|
be 105ms. |
|
|
|
|
|
|
|
Note: To prevent the `poll` phase from starving the event loop, libuv |
|
|
|
also has a hard maximum (system dependent) before it stops `poll`ing for |
|
|
|
Note: To prevent the **poll** phase from starving the event loop, libuv |
|
|
|
also has a hard maximum (system dependent) before it stops polling for |
|
|
|
more events. |
|
|
|
|
|
|
|
### I/O callbacks: |
|
|
|
### I/O callbacks |
|
|
|
|
|
|
|
This phase executes callbacks for some system operations such as types |
|
|
|
of TCP errors. For example if a TCP socket receives `ECONNREFUSED` when |
|
|
|
attempting to connect, some \*nix systems want to wait to report the |
|
|
|
error. This will be queued to execute in the `I/O callbacks` phase. |
|
|
|
|
|
|
|
### poll: |
|
|
|
error. This will be queued to execute in the **I/O callbacks** phase. |
|
|
|
|
|
|
|
The poll phase has two main functions: |
|
|
|
### poll |
|
|
|
|
|
|
|
1. Executing scripts for timers who's threshold has elapsed, then |
|
|
|
2. Processing events in the `poll` queue. |
|
|
|
The **poll** phase has two main functions: |
|
|
|
|
|
|
|
1. Executing scripts for timers whose threshold has elapsed, then |
|
|
|
2. Processing events in the **poll** queue. |
|
|
|
|
|
|
|
When the event loop enters the `poll` phase _and there are no timers |
|
|
|
When the event loop enters the **poll** phase _and there are no timers |
|
|
|
scheduled_, one of two things will happen: |
|
|
|
|
|
|
|
* _If the `poll` queue **is not empty**_, the event loop will iterate |
|
|
|
through its queue of callbacks executing them synchronously until |
|
|
|
either the queue has been exhausted, or the system-dependent hard limit |
|
|
|
* _If the **poll** queue **is not empty**_, the event loop will iterate |
|
|
|
through its queue of callbacks executing them synchronously until |
|
|
|
either the queue has been exhausted, or the system-dependent hard limit |
|
|
|
is reached. |
|
|
|
|
|
|
|
* _If the `poll` queue **is empty**_, one of two more things will |
|
|
|
* _If the **poll** queue **is empty**_, one of two more things will |
|
|
|
happen: |
|
|
|
* If scripts have been scheduled by `setImmediate()`, the event loop |
|
|
|
will end the `poll` phase and continue to the `check` phase to |
|
|
|
will end the **poll** phase and continue to the **check** phase to |
|
|
|
execute those scheduled scripts. |
|
|
|
|
|
|
|
* If scripts **have not** been scheduled by `setImmediate()`, the |
|
|
|
event loop will wait for callbacks to be added to the queue, then |
|
|
|
execute it immediately. |
|
|
|
execute them immediately. |
|
|
|
|
|
|
|
Once the `poll` queue is empty the event loop will check for timers |
|
|
|
Once the **poll** queue is empty the event loop will check for timers |
|
|
|
_whose time thresholds have been reached_. If one or more timers are |
|
|
|
ready, the event loop will wrap back to the timers phase to execute |
|
|
|
ready, the event loop will wrap back to the **timers** phase to execute |
|
|
|
those timers' callbacks. |
|
|
|
|
|
|
|
### `check`: |
|
|
|
### check |
|
|
|
|
|
|
|
This phase allows a person to execute callbacks immediately after the |
|
|
|
`poll` phase has completed. If the `poll` phase becomes idle and |
|
|
|
scripts have been queued with `setImmediate()`, the event loop may |
|
|
|
continue to the `check` phase rather than waiting. |
|
|
|
This phase allows a person to execute callbacks immediately after the |
|
|
|
**poll** phase has completed. If the **poll** phase becomes idle and |
|
|
|
scripts have been queued with `setImmediate()`, the event loop may |
|
|
|
continue to the **check** phase rather than waiting. |
|
|
|
|
|
|
|
`setImmediate()` is actually a special timer that runs in a separate |
|
|
|
phase of the event loop. It uses a libuv API that schedules callbacks to |
|
|
|
execute after the `poll` phase has completed. |
|
|
|
execute after the **poll** phase has completed. |
|
|
|
|
|
|
|
Generally, as the code is executed, the event loop will eventually hit |
|
|
|
the `poll` phase where it will wait for an incoming connection, request, |
|
|
|
etc. However, after a callback has been scheduled with `setImmediate()`, |
|
|
|
then the `poll` phase becomes idle, it will end and continue to the |
|
|
|
`check` phase rather than waiting for `poll` events. |
|
|
|
the **poll** phase where it will wait for an incoming connection, request, |
|
|
|
etc. However, if a callback has been scheduled with `setImmediate()` |
|
|
|
and the **poll** phase becomes idle, it will end and continue to the |
|
|
|
**check** phase rather than waiting for **poll** events. |
|
|
|
|
|
|
|
### `close callbacks`: |
|
|
|
### close callbacks |
|
|
|
|
|
|
|
If a socket or handle is closed abruptly (e.g. `socket.destroy()`), the |
|
|
|
`'close'` event will be emitted in this phase. Otherwise it will be |
|
|
@ -214,12 +211,12 @@ emitted via `process.nextTick()`. |
|
|
|
ways depending on when they are called. |
|
|
|
|
|
|
|
* `setImmediate()` is designed to execute a script once the current |
|
|
|
`poll` phase completes. |
|
|
|
* `setTimeout()` schedules a script to be run |
|
|
|
after a minimum threshold in ms has elapsed. |
|
|
|
**poll** phase completes. |
|
|
|
* `setTimeout()` schedules a script to be run after a minimum threshold |
|
|
|
in ms has elapsed. |
|
|
|
|
|
|
|
The order in which the timers are executed will vary depending on the |
|
|
|
context in which they are called. If both are called from within the |
|
|
|
context in which they are called. If both are called from within the |
|
|
|
main module, then timing will be bound by the performance of the process |
|
|
|
(which can be impacted by other applications running on the machine). |
|
|
|
|
|
|
@ -248,7 +245,6 @@ setImmediate(function immediate () { |
|
|
|
immediate |
|
|
|
timeout |
|
|
|
|
|
|
|
|
|
|
|
However, if you move the two calls within an I/O cycle, the immediate |
|
|
|
callback is always executed first: |
|
|
|
|
|
|
@ -278,22 +274,22 @@ The main advantage to using `setImmediate()` over `setTimeout()` is |
|
|
|
`setImmediate()` will always be executed before any timers if scheduled |
|
|
|
within an I/O cycle, independently of how many timers are present. |
|
|
|
|
|
|
|
## `process.nextTick()`: |
|
|
|
## `process.nextTick()` |
|
|
|
|
|
|
|
### Understanding `process.nextTick()` |
|
|
|
|
|
|
|
You may have noticed that `process.nextTick()` was not displayed in the |
|
|
|
diagram, even though its a part of the asynchronous API. This is because |
|
|
|
diagram, even though it's a part of the asynchronous API. This is because |
|
|
|
`process.nextTick()` is not technically part of the event loop. Instead, |
|
|
|
the nextTickQueue will be processed after the current operation |
|
|
|
completes, regardless of the current `phase` of the event loop. |
|
|
|
the `nextTickQueue` will be processed after the current operation |
|
|
|
completes, regardless of the current phase of the event loop. |
|
|
|
|
|
|
|
Looking back at our diagram, any time you call `process.nextTick()` in a |
|
|
|
given phase, all callbacks passed to `process.nextTick()` will be |
|
|
|
resolved before the event loop continues. This can create some bad |
|
|
|
situations because **it allows you to "starve" your I/O by making |
|
|
|
recursive `process.nextTick()` calls.** which prevents the event loop |
|
|
|
from reaching the `poll` phase. |
|
|
|
recursive `process.nextTick()` calls**, which prevents the event loop |
|
|
|
from reaching the **poll** phase. |
|
|
|
|
|
|
|
### Why would that be allowed? |
|
|
|
|
|
|
@ -319,9 +315,9 @@ What we're doing is passing an error back to the user but only *after* |
|
|
|
we have allowed the rest of the user's code to execute. By using |
|
|
|
`process.nextTick()` we guarantee that `apiCall()` always runs its |
|
|
|
callback *after* the rest of the user's code and *before* the event loop |
|
|
|
is allowed to proceed. To acheive this, the JS call stack is allowed to |
|
|
|
is allowed to proceed. To achieve this, the JS call stack is allowed to |
|
|
|
unwind then immediately execute the provided callback which allows a |
|
|
|
person to make recursive calls to nextTick without reaching a |
|
|
|
person to make recursive calls to `process.nextTick()` without reaching a |
|
|
|
`RangeError: Maximum call stack size exceeded from v8`. |
|
|
|
|
|
|
|
This philosophy can lead to some potentially problematic situations. |
|
|
@ -343,21 +339,33 @@ var bar = 1; |
|
|
|
``` |
|
|
|
|
|
|
|
The user defines `someAsyncApiCall()` to have an asynchronous signature, |
|
|
|
actually operates synchronously. When it is called, the callback |
|
|
|
provided to `someAsyncApiCall ()` is called in the same phase of the |
|
|
|
but it actually operates synchronously. When it is called, the callback |
|
|
|
provided to `someAsyncApiCall()` is called in the same phase of the |
|
|
|
event loop because `someAsyncApiCall()` doesn't actually do anything |
|
|
|
asynchronously. As a result, the callback tries to reference `bar` but |
|
|
|
it may not have that variable in scope yet because the script has not |
|
|
|
asynchronously. As a result, the callback tries to reference `bar` even |
|
|
|
though it may not have that variable in scope yet, because the script has not |
|
|
|
been able to run to completion. |
|
|
|
|
|
|
|
By placing it in a `process.nextTick()`, the script still has the |
|
|
|
By placing the callback in a `process.nextTick()`, the script still has the |
|
|
|
ability to run to completion, allowing all the variables, functions, |
|
|
|
etc., to be initialized prior to the callback being called. It also has |
|
|
|
etc., to be initialized prior to the callback being called. It also has |
|
|
|
the advantage of not allowing the event loop to continue. It may be |
|
|
|
useful that the user be alerted to an error before the event loop is |
|
|
|
allowed to continue. |
|
|
|
useful for the user to be alerted to an error before the event loop is |
|
|
|
allowed to continue. Here is the previous example using `process.nextTick()`: |
|
|
|
|
|
|
|
```js |
|
|
|
function someAsyncApiCall (callback) { |
|
|
|
process.nextTick(callback); |
|
|
|
}; |
|
|
|
|
|
|
|
someAsyncApiCall(() => { |
|
|
|
console.log('bar', bar); // 1 |
|
|
|
}); |
|
|
|
|
|
|
|
var bar = 1; |
|
|
|
``` |
|
|
|
|
|
|
|
A real world example in node would be: |
|
|
|
Here's another real world example: |
|
|
|
|
|
|
|
```js |
|
|
|
const server = net.createServer(() => {}).listen(8080); |
|
|
@ -367,10 +375,10 @@ server.on('listening', () => {}); |
|
|
|
|
|
|
|
When only a port is passed the port is bound immediately. So the |
|
|
|
`'listening'` callback could be called immediately. Problem is that the |
|
|
|
`.on('listening')` will not have been set by that time. |
|
|
|
`.on('listening')` will not have been set by that time. |
|
|
|
|
|
|
|
To get around this the `'listening'` event is queued in a `nextTick()` |
|
|
|
to allow the script to run to completion. Which allows the user to set |
|
|
|
to allow the script to run to completion. Which allows the user to set |
|
|
|
any event handlers they want. |
|
|
|
|
|
|
|
## `process.nextTick()` vs `setImmediate()` |
|
|
@ -389,7 +397,7 @@ percentage of the packages on npm. Every day more new modules are being |
|
|
|
added, which mean every day we wait, more potential breakages occur. |
|
|
|
While they are confusing, the names themselves won't change. |
|
|
|
|
|
|
|
*We recommend developers use `setImmediate()` in all cases because its |
|
|
|
*We recommend developers use `setImmediate()` in all cases because it's |
|
|
|
easier to reason about (and it leads to code that's compatible with a |
|
|
|
wider variety of environments, like browser JS.)* |
|
|
|
|
|
|
@ -413,11 +421,11 @@ server.listen(8080); |
|
|
|
server.on('listening', function() { }); |
|
|
|
``` |
|
|
|
|
|
|
|
Say that listen() is run at the beginning of the event loop, but the |
|
|
|
Say that `listen()` is run at the beginning of the event loop, but the |
|
|
|
listening callback is placed in a `setImmediate()`. Now, unless a |
|
|
|
hostname is passed binding to the port will happen immediately. Now for |
|
|
|
the event loop to proceed it must hit the `poll` phase, which means |
|
|
|
there is a non-zero chance that a connection could have been received |
|
|
|
hostname is passed binding to the port will happen immediately. Now for |
|
|
|
the event loop to proceed it must hit the **poll** phase, which means |
|
|
|
there is a non-zero chance that a connection could have been received |
|
|
|
allowing the connection event to be fired before the listening event. |
|
|
|
|
|
|
|
Another example is running a function constructor that was to, say, |
|
|
|