Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
46f0702
doc: add topic - event loop, timers, `nextTick()`
techjeffharris Jan 28, 2016
5a28415
doc: add topic - event loop, timers, `nextTick()`
techjeffharris Jan 28, 2016
dc1b8a5
corrections suggested in the GitHub PR
techjeffharris Jan 29, 2016
bb5b682
Merge branch 'doc-topic-event-loop-timers-nextTick' of github.com:tec…
techjeffharris Jan 29, 2016
ba98380
removed file without .md extension
techjeffharris Jan 29, 2016
936bf17
add details to explanation of timers
techjeffharris Jan 29, 2016
35cf726
update to address comments on PR
techjeffharris Feb 17, 2016
f80d7cc
fixed typo, added example as per @trevnorris
techjeffharris Feb 24, 2016
254694b
fixed styling nits identified by @mscdex
techjeffharris Feb 24, 2016
45fb2fe
fixes suggested by @silverwind and @fishrock123
techjeffharris Feb 26, 2016
d6d76f5
addressed comments made on GH issue
techjeffharris Mar 25, 2016
c133caf
updated `setImmediate()` vs `setTimeout()` section
techjeffharris Mar 25, 2016
f425164
update overview, phase detail headings, wrap at 72
techjeffharris Mar 29, 2016
1bd3e6c
docs: minor nits on the libuv phases.
mcollina Mar 31, 2016
7574d4b
Removed second timer phase.
mcollina Mar 31, 2016
8dc6ecb
Merge pull request #1 from mcollina/doc-topic-event-loop-timers-nextTick
techjeffharris Mar 31, 2016
d82a7f1
fix nits presented by @ajafff
techjeffharris Mar 31, 2016
1dc26f6
fix backticks on line 205
techjeffharris Mar 31, 2016
82d0fb8
Improve wording `setTimeout()` vs `setImmediate()`
techjeffharris Apr 7, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
corrections suggested in the GitHub PR
  • Loading branch information
techjeffharris committed Jan 29, 2016
commit dc1b8a5bc759753b60cf6c1f8ea93890e5333919
175 changes: 90 additions & 85 deletions doc/topics/the-event-loop-timers-and-nexttick
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Overview of the Event Loop, Timers, and `process.nextTick()`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should end with a .md extension.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooops >_<


The Following diagram shows a simplified overview of the event loop's order of operations.
The Following diagram shows a simplified overview of the event loop's
order of operations.

┌───────────────────────┐
┌─>│ timers │
Expand All @@ -15,24 +16,25 @@ The Following diagram shows a simplified overview of the event loop's order of o
└──│ setImmediate │
└───────────────────────┘

note: each box will be referred to as a "phase" of the event loop.
*note: each box will be referred to as a "phase" of the event loop.*

*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 actually uses are these four.*
ones we care aboutones that Node.js actually uses are these four.*

## timers

This phase executes callbacks scheduled by `setTimeout()` and `setInterval()`.
When you create a timer, you make a call to setTimeout(). The event loop will
eventually enter the 'poll' phase which determines how many milliseconds remain
until the next timer. If there is a timer, it will wait for connections for that
many milliseconds. After that many milliseconds, it will break the 'poll' phase
and wrap back around to the timers phase where those callbacks can be processed.

*Note: The 'poll' phase technically controls when timers are called due to its
ability to cause a thread to sit idly without burning CPU in order to stall the
When you create a timer, you make a call to `setTimeout()`. The event loop
will eventually enter the `poll` phase which determines how many milliseconds
remain until the next timer. If there is a timer, it will wait for connections
for that many milliseconds. After that many milliseconds, it will break the
`poll` phase and wrap back around to the timers phase where those callbacks
can be processed.

*Note: The `poll` phase technically controls when timers are called due to its
ability to cause a thread to sit idly without burning CU in order to stall the
event loop so the timer can execute.*

## pending callbacks:
Expand All @@ -42,84 +44,83 @@ This phase executes callbacks for specific types of TCP errors, for example.
## poll:

This is the phase in which the event loop sits and waits for incoming
connections to be received. Ideally, most scripts spend most of their time here.
connections to be received. Ideally, most scripts spend most of their time
here.

## setImmediate:
## setImmediate():

`process.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.
`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.

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()`, at the start
of the poll phase, a check will be run to see if there are any callbacks
waiting. If there are none waiting, the poll phase will end and continue to the
`setImmediate` callback phase.
`poll` phase where it will wait for an incoming connection, request, etc.
However, after a callback has been scheduled with `setImmediate()`, at the
start of the poll phase, a check will be run to see if there are any callbacks
waiting. If there are none waiting, the poll phase will end and continue to
the `setImmediate` callback phase.

### setImmediate vs setTimeout
### `setImmediate()` vs `setTimeout()`

How quickly a `setImmediate()` callback is executed is only limited by how
quickly the event loop can be processed whereas a timer won't fire until the
number of milliseconds passed have elapsed.

The advantage to using `setImmediate()` over `setTimeout()` is that the lowest
value you may set a timer's delay to is 1 ms (0 is coerced to 1), which doesn't
seem like much time to us humans, but it's actually pretty slow compared to how
quickly `setImmediate()` can execute--the event loop operates on the microsecond
scale (1 ms = 1000 µs).
value you may set a timer's delay to is 1 ms (0 is coerced to 1) which doesn't
seem like much time to us humans, but it's actually pretty slow compared to
how quickly `setImmediate()` can executethe event loop operates on the
microsecond scale (1 ms = 1000 µs.)

## nextTick:
## `process.nextTick()`:

### Understanding nextTick()
### Understanding `process.nextTick()`

You may have noticed that `nextTick()` was not displayed in the diagram, even
though its a part of the asynchronous API. This is because nextTick is not
technically part of the event loop. Instead, it is executed at the end of each
phase of the event loop.
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
`process.nextTick()` is not technically part of the event loop. Instead, it
is executed at the end of each phase of the event loop.

Looking back at our diagram, any time you call nextTick in a given phase, all
callbacks passed to nextTick will be resolved before the event loop continues.
This can create some bad situations because **it allows you to asynchronously
"starve" your I/O by making recursive nextTick calls.** which prevents the
event loop from reaching the poll phase.
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.

### Why would that be allowed?

Why would something like this be included in Node? Part of it is a design
philosophy where an API should always be asynchronous even where it
doesn't have to be. Take this code snippet for example:
Why would something like this be included in Node.js? Part of it is a design
philosophy where an API should always be asynchronous even where it doesn't
have to be. Take this code snippet for example:

```javascript
```js
function apiCall (arg, callback) {
if (typeof arg !== 'string')
return process.nextTick(
callback,
new TypeError('argument should be a string'));
return process.nextTick(callback,
new TypeError('argument should be string'));
}
```

The snippet does an argument check and if its not correct, it will pass the
The snippet does an argument check and if it's not correct, it will pass the
error to the callback. The API updated fairly recently to allow passing
arguments to nextTick allowing it to take any arguments passed after the callback
to be propagated as the arguments to the callback so you don't have to nest functions.
arguments to `process.nextTick()` allowing it to take any arguments passed
after the callback to be propagated as the arguments to the callback so you
don't have to nest functions.

What we're doing is passing an error back to the user. As far as the event loop
is concerned, its happening **synchronously**, but as far as the user is
concerned, it is happening **asynchronously** because the API of apiCall() was
written to always be asynchronous.
What we're doing is passing an error back to the user. As far as the _event
loop_ is concerned, this happens **synchronously**. However, as far as the
_user_ is concerned, it occurs **asynchronously**: `apiCall()` always runs its
callback *after* the rest of the user's code.

This philosophy can lead to some potentially problematic situations. Take this
snippet for example:
This philosophy can lead to some potentially problematic situations. Take
this snippet for example:

```javascript
```js
// this has an asynchronous signature, but calls callback synchronously
function someAsyncApiCall (callback) {
callback();
};
function someAsyncApiCall (callback) { callback(); };

// the callback is called before `someAsyncApiCall` completes.
someAsyncApiCall(function () {
someAsyncApiCall(() => {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use ES2015 for our examples? IE fat arrow functions?

// since someAsyncApiCall has completed, bar hasn't been assigned any value
console.log('bar', bar); // undefined
Expand All @@ -131,44 +132,48 @@ 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 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 been able to run to completion.
`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 been able to run to
completion.

By placing it in a 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 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.
By placing it 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 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.

## process.nextTick() vs setImmediate()
## process.nextTick() vs `setImmediate()`

We have two calls that are similar as far as users are concerned, but their
names are confusing.

* nextTick fires immediately on the same phase
* setImmediate fires on the following iteration or 'tick' of the event loop
* `process.nextTick()` fires immediately on the same phase
* `setImmediate()` fires on the following iteration or 'tick' of the event
loop

In essence, the names should be swapped. nextTick fires more immediately than
setImmediate but this is an artifact of the past which is unlikely to change.
Making this switch would break a large 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.
In essence, the names should be swapped. `process.nextTick()` fires more
immediately than `setImmediate()` but this is an artifact of the past which is
unlikely to change. Making this switch would break a large 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 easier to
reason about.*
*We recommend developers use `setImmediate()` in all cases because its easier
to reason about (and it leads to code that's compatible with a wider
variety of environments, like browser JS.)*

## Two reasons to use nextTick:
## Two reasons to use `process.nextTick()`:

1. Allow users to handle errors, cleanup any then unneeded resources, or
perhaps try the request again before the event loop continues.
perhaps try the request again before the event loop continues.

2. If you were to run a function constructor that was to, say, inherit from
`EventEmitter` and it wanted to call an event within the constructor. You can't
emit an event from the constructor immediately because the script will not have
processed to the point where the user assigns a callback to that event. So,
within the constructor itself, you can set a callback to emit the event after
the constructor has finished, which provides the expected results.
`EventEmitter` and it wanted to call an event within the constructor. You
can't emit an event from the constructor immediately because the script
will not have processed to the point where the user assigns a callback to
that event. So, within the constructor itself, you can use
`process.nextTick()` to set a callback to emit the event after the
constructor has finished, which provides the expected results.

Loading