-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
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
Changes from 1 commit
46f0702
5a28415
dc1b8a5
bb5b682
ba98380
936bf17
35cf726
f80d7cc
254694b
45fb2fe
d6d76f5
c133caf
f425164
1bd3e6c
7574d4b
8dc6ecb
d82a7f1
1dc26f6
82d0fb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| # Overview of the Event Loop, Timers, and `process.nextTick()` | ||
|
|
||
| The Following diagram shows a simplified overview of the event loop's order of operations. | ||
|
|
||
| ┌───────────────────────┐ | ||
| ┌─>│ timers │ | ||
| │ └──────────┬────────────┘ | ||
| │ ┌──────────┴────────────┐ | ||
| │ │ pending callbacks │ | ||
| │ └──────────┬────────────┘ ┌───────────────┐ | ||
| │ ┌──────────┴────────────┐ │ incoming: │ | ||
| │ │ poll │<─────│ connections, │ | ||
| │ └──────────┬────────────┘ │ data, etc. │ | ||
| │ ┌──────────┴────────────┐ └───────────────┘ | ||
| └──│ setImmediate │ | ||
| └───────────────────────┘ | ||
|
|
||
| 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.* | ||
|
||
|
|
||
| ## 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 | ||
| event loop so the timer can execute.* | ||
|
|
||
| ## pending callbacks: | ||
|
|
||
| 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. | ||
|
|
||
| ## 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. | ||
|
|
||
| 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. | ||
|
|
||
| ### 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). | ||
|
|
||
| ## nextTick: | ||
|
|
||
| ### Understanding 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. | ||
|
|
||
| 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. | ||
|
|
||
| ### 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: | ||
|
|
||
| ```javascript | ||
| function apiCall (arg, callback) { | ||
| if (typeof arg !== 'string') | ||
| return process.nextTick( | ||
| callback, | ||
| new TypeError('argument should be a string')); | ||
| } | ||
| ``` | ||
|
|
||
| The snippet does an argument check and if its 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. | ||
|
|
||
| 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. | ||
|
|
||
| This philosophy can lead to some potentially problematic situations. Take this | ||
| snippet for example: | ||
|
|
||
| ```javascript | ||
| // this has an asynchronous signature, but calls callback synchronously | ||
| function someAsyncApiCall (callback) { | ||
| callback(); | ||
| }; | ||
|
|
||
| // the callback is called before `someAsyncApiCall` completes. | ||
| someAsyncApiCall(function () { | ||
|
|
||
|
||
| // since someAsyncApiCall has completed, bar hasn't been assigned any value | ||
| console.log('bar', bar); // undefined | ||
|
|
||
| }); | ||
|
|
||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| ## 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 | ||
|
|
||
| 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. | ||
|
|
||
| *We recommend developers use setImmediate in all cases because its easier to | ||
| reason about.* | ||
|
||
|
|
||
| ## Two reasons to use nextTick: | ||
|
|
||
| 1. Allow users to handle errors, cleanup any then unneeded resources, or | ||
| 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. | ||
|
||
|
|
||
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 >_<