Browse Source

doc: grammar fixes to event loop guide

PR-URL: https://github.com/nodejs/node/pull/7479
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
v6.x
Ryan Lewis 9 years ago
committed by Evan Lucas
parent
commit
cc7fdf429e
  1. 146
      doc/topics/the-event-loop-timers-and-nexttick.md

146
doc/topics/the-event-loop-timers-and-nexttick.md

@ -9,7 +9,7 @@ offloading operations to the system kernel whenever possible.
Since most modern kernels are multi-threaded, they can handle multiple Since most modern kernels are multi-threaded, they can handle multiple
operations executing in the background. When one of these operations operations executing in the background. When one of these operations
completes, the kernel tells Node.js so that the appropriate callback 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. this in further detail later in this topic.
## Event Loop Explained ## Event Loop Explained
@ -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 special in its own way, generally, when the event loop enters a given
phase, it will perform any operations specific to that phase, then phase, it will perform any operations specific to that phase, then
execute callbacks in that phase's queue until the queue has been 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 queue has been exhausted or the callback limit is reached, the event
loop will move to the next phase, and so on. loop will move to the next phase, and so on.
Since any of these operations may schedule _more_ operations and new 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 events can be queued while polling events are being processed. As a
result, long running callbacks can allow the poll phase to run much result, long running callbacks can allow the poll phase to run much
longer than a timer's threshold. See the [`timers`](#timers) and longer than a timer's threshold. See the [**timers**](#timers) and
[`poll`](#poll) sections for more details. [**poll**](#poll) sections for more details.
_**NOTE:** There is a slight discrepancy between the Windows and the _**NOTE:** There is a slight discrepancy between the Windows and the
Unix/Linux implementation, but that's not important for this Unix/Linux implementation, but that's not important for this
demonstration. The most important parts are here. There are actually demonstration. The most important parts are here. There are actually
seven or eight steps, but the ones we care about — ones that Node.js 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()`. and `setInterval()`.
* `I/O callbacks`: most types of callback except timers, setImmedate, close * **I/O callbacks**: most types of callback except timers, `setImmedate()`, close
* `idle, prepare`: only used internally * **idle, prepare**: only used internally
* `poll`: retrieve new I/O events; node will block here when appropriate * **poll**: retrieve new I/O events; node will block here when appropriate
* `check`: setImmediate callbacks are invoked here * **check**: `setImmediate()` callbacks are invoked here
* `close callbacks`: e.g socket.on('close', ...) * **close callbacks**: e.g socket.on('close', ...)
Between each run of the event loop, Node.js checks if it is waiting for 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. any.
## Phases in Detail ## 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 Operating System scheduling or the running of other callbacks may delay
them. them.
_**Note**: Technically, the [`poll` phase](#poll) controls when timers _**Note**: Technically, the [**poll** phase](#poll) controls when timers
are executed._ are executed._
For example, say you schedule a timeout to execute after a 100 ms For example, say you schedule a timeout to execute after a 100 ms
@ -102,10 +102,8 @@ takes 95 ms:
var fs = require('fs'); var fs = require('fs');
function someAsyncOperation (callback) { function someAsyncOperation (callback) {
// Assume this takes 95ms to complete
// let's assume this takes 95ms to complete
fs.readFile('/path/to/file', callback); fs.readFile('/path/to/file', callback);
} }
var timeoutScheduled = Date.now(); var timeoutScheduled = Date.now();
@ -131,78 +129,77 @@ someAsyncOperation(function () {
}); });
``` ```
When the event loop enters the `poll` phase, it has an empty queue 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 (`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 remaining until the soonest timer's threshold is reached. While it is
waiting 95 ms pass, `fs.readFile()` finishes reading the file and its 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 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 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 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. be 105ms.
Note: To prevent the `poll` phase from starving the event loop, libuv 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 also has a hard maximum (system dependent) before it stops polling for
more events. more events.
### I/O callbacks: ### I/O callbacks
This phase executes callbacks for some system operations such as types This phase executes callbacks for some system operations such as types
of TCP errors. For example if a TCP socket receives `ECONNREFUSED` when of TCP errors. For example if a TCP socket receives `ECONNREFUSED` when
attempting to connect, some \*nix systems want to wait to report the 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. error. This will be queued to execute in the **I/O callbacks** phase.
### poll:
The poll phase has two main functions: ### poll
1. Executing scripts for timers who's threshold has elapsed, then The **poll** phase has two main functions:
2. Processing events in the `poll` queue.
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: scheduled_, one of two things will happen:
* _If the `poll` queue **is not empty**_, the event loop will iterate * _If the **poll** queue **is not empty**_, the event loop will iterate
through its queue of callbacks executing them synchronously until through its queue of callbacks executing them synchronously until
either the queue has been exhausted, or the system-dependent hard limit either the queue has been exhausted, or the system-dependent hard limit
is reached. 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: happen:
* If scripts have been scheduled by `setImmediate()`, the event loop * 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. execute those scheduled scripts.
* If scripts **have not** been scheduled by `setImmediate()`, the * If scripts **have not** been scheduled by `setImmediate()`, the
event loop will wait for callbacks to be added to the queue, then 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 _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. those timers' callbacks.
### `check`: ### check
This phase allows a person to execute callbacks immediately after the This phase allows a person to execute callbacks immediately after the
`poll` phase has completed. If the `poll` phase becomes idle and **poll** phase has completed. If the **poll** phase becomes idle and
scripts have been queued with `setImmediate()`, the event loop may scripts have been queued with `setImmediate()`, the event loop may
continue to the `check` phase rather than waiting. continue to the **check** phase rather than waiting.
`setImmediate()` is actually a special timer that runs in a separate `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 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 Generally, as the code is executed, the event loop will eventually hit
the `poll` phase where it will wait for an incoming connection, request, the **poll** phase where it will wait for an incoming connection, request,
etc. However, after a callback has been scheduled with `setImmediate()`, etc. However, if a callback has been scheduled with `setImmediate()`
then the `poll` phase becomes idle, it will end and continue to the and the **poll** phase becomes idle, it will end and continue to the
`check` phase rather than waiting for `poll` events. **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 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 `'close'` event will be emitted in this phase. Otherwise it will be
@ -214,9 +211,9 @@ emitted via `process.nextTick()`.
ways depending on when they are called. ways depending on when they are called.
* `setImmediate()` is designed to execute a script once the current * `setImmediate()` is designed to execute a script once the current
`poll` phase completes. **poll** phase completes.
* `setTimeout()` schedules a script to be run * `setTimeout()` schedules a script to be run after a minimum threshold
after a minimum threshold in ms has elapsed. in ms has elapsed.
The order in which the timers are executed will vary depending on the 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
@ -248,7 +245,6 @@ setImmediate(function immediate () {
immediate immediate
timeout timeout
However, if you move the two calls within an I/O cycle, the immediate However, if you move the two calls within an I/O cycle, the immediate
callback is always executed first: 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 `setImmediate()` will always be executed before any timers if scheduled
within an I/O cycle, independently of how many timers are present. within an I/O cycle, independently of how many timers are present.
## `process.nextTick()`: ## `process.nextTick()`
### Understanding `process.nextTick()` ### Understanding `process.nextTick()`
You may have noticed that `process.nextTick()` was not displayed in the 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, `process.nextTick()` is not technically part of the event loop. Instead,
the nextTickQueue will be processed after the current operation the `nextTickQueue` will be processed after the current operation
completes, regardless of the current `phase` of the event loop. completes, regardless of the current phase of the event loop.
Looking back at our diagram, any time you call `process.nextTick()` in a Looking back at our diagram, any time you call `process.nextTick()` in a
given phase, all callbacks passed to `process.nextTick()` will be given phase, all callbacks passed to `process.nextTick()` will be
resolved before the event loop continues. This can create some bad resolved before the event loop continues. This can create some bad
situations because **it allows you to "starve" your I/O by making situations because **it allows you to "starve" your I/O by making
recursive `process.nextTick()` calls.** which prevents the event loop recursive `process.nextTick()` calls**, which prevents the event loop
from reaching the `poll` phase. from reaching the **poll** phase.
### Why would that be allowed? ### 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 we have allowed the rest of the user's code to execute. By using
`process.nextTick()` we guarantee that `apiCall()` always runs its `process.nextTick()` we guarantee that `apiCall()` always runs its
callback *after* the rest of the user's code and *before* the event loop 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 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`. `RangeError: Maximum call stack size exceeded from v8`.
This philosophy can lead to some potentially problematic situations. 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, The user defines `someAsyncApiCall()` to have an asynchronous signature,
actually operates synchronously. When it is called, the callback but it actually operates synchronously. When it is called, the callback
provided to `someAsyncApiCall()` is called in the same phase of the provided to `someAsyncApiCall()` is called in the same phase of the
event loop because `someAsyncApiCall()` doesn't actually do anything event loop because `someAsyncApiCall()` doesn't actually do anything
asynchronously. As a result, the callback tries to reference `bar` but asynchronously. As a result, the callback tries to reference `bar` even
it may not have that variable in scope yet because the script has not though it may not have that variable in scope yet, because the script has not
been able to run to completion. 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, 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 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 useful for the user to be alerted to an error before the event loop is
allowed to continue. 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 ```js
const server = net.createServer(() => {}).listen(8080); const server = net.createServer(() => {}).listen(8080);
@ -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. added, which mean every day we wait, more potential breakages occur.
While they are confusing, the names themselves won't change. 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 easier to reason about (and it leads to code that's compatible with a
wider variety of environments, like browser JS.)* wider variety of environments, like browser JS.)*
@ -413,10 +421,10 @@ server.listen(8080);
server.on('listening', function() { }); 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 listening callback is placed in a `setImmediate()`. Now, unless a
hostname is passed binding to the port will happen immediately. Now for 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 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 there is a non-zero chance that a connection could have been received
allowing the connection event to be fired before the listening event. allowing the connection event to be fired before the listening event.

Loading…
Cancel
Save