-
-
Notifications
You must be signed in to change notification settings - Fork 35k
doc: add topic - event loop, timers, nextTick()
#4936
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
calvinmetcalf
merged 19 commits into
nodejs:master
from
techjeffharris:doc-topic-event-loop-timers-nextTick
Apr 8, 2016
Merged
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 5a28415
doc: add topic - event loop, timers, `nextTick()`
techjeffharris dc1b8a5
corrections suggested in the GitHub PR
techjeffharris bb5b682
Merge branch 'doc-topic-event-loop-timers-nextTick' of github.com:tec…
techjeffharris ba98380
removed file without .md extension
techjeffharris 936bf17
add details to explanation of timers
techjeffharris 35cf726
update to address comments on PR
techjeffharris f80d7cc
fixed typo, added example as per @trevnorris
techjeffharris 254694b
fixed styling nits identified by @mscdex
techjeffharris 45fb2fe
fixes suggested by @silverwind and @fishrock123
techjeffharris d6d76f5
addressed comments made on GH issue
techjeffharris c133caf
updated `setImmediate()` vs `setTimeout()` section
techjeffharris f425164
update overview, phase detail headings, wrap at 72
techjeffharris 1bd3e6c
docs: minor nits on the libuv phases.
mcollina 7574d4b
Removed second timer phase.
mcollina 8dc6ecb
Merge pull request #1 from mcollina/doc-topic-event-loop-timers-nextTick
techjeffharris d82a7f1
fix nits presented by @ajafff
techjeffharris 1dc26f6
fix backticks on line 205
techjeffharris 82d0fb8
Improve wording `setTimeout()` vs `setImmediate()`
techjeffharris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
corrections suggested in the GitHub PR
- Loading branch information
commit dc1b8a5bc759753b60cf6c1f8ea93890e5333919
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| # Overview of the Event Loop, Timers, and `process.nextTick()` | ||
|
|
||
| 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 │ | ||
|
|
@@ -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 about — ones 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: | ||
|
|
@@ -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 execute — the 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(() => { | ||
|
|
||
|
||
| // since someAsyncApiCall has completed, bar hasn't been assigned any value | ||
| console.log('bar', bar); // undefined | ||
|
|
@@ -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. | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
.mdextension.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooops >_<