From 46f07025e1088797df103bdfa09ab4d988607c9a Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Thu, 28 Jan 2016 14:57:53 -0800 Subject: [PATCH 01/17] doc: add topic - event loop, timers, `nextTick()` This is a rough draft of a [Topic](https://github.com/nodejs/docs/blob/master/GETTING-STARTED.md#what-we-write) that provides an overview of the event loop, timers, and `process.nextTick()` that is based upon a NodeSource "Need to Node" presentation hosted by @trevnorris: [Event Scheduling and the Node.js Event Loop](https://nodesource.com/resources). --- doc/topics/the-event-loop-timers-and-nexttick | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 doc/topics/the-event-loop-timers-and-nexttick diff --git a/doc/topics/the-event-loop-timers-and-nexttick b/doc/topics/the-event-loop-timers-and-nexttick new file mode 100644 index 00000000000000..8001affb643bd1 --- /dev/null +++ b/doc/topics/the-event-loop-timers-and-nexttick @@ -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. + From 5a28415365a122ad990db3a3fdf81afac45313bd Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Thu, 28 Jan 2016 14:57:53 -0800 Subject: [PATCH 02/17] doc: add topic - event loop, timers, `nextTick()` This is a rough draft of a [Topic](https://github.com/nodejs/docs/blob/master/GETTING-STARTED.md#what-we-write) that provides an overview of the event loop, timers, and `process.nextTick()` that is based upon a NodeSource "Need to Node" presentation hosted by @trevnorris: [Event Scheduling and the Node.js Event Loop](https://nodesource.com/resources). --- doc/topics/the-event-loop-timers-and-nexttick | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 doc/topics/the-event-loop-timers-and-nexttick diff --git a/doc/topics/the-event-loop-timers-and-nexttick b/doc/topics/the-event-loop-timers-and-nexttick new file mode 100644 index 00000000000000..8001affb643bd1 --- /dev/null +++ b/doc/topics/the-event-loop-timers-and-nexttick @@ -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. + From dc1b8a5bc759753b60cf6c1f8ea93890e5333919 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Fri, 29 Jan 2016 11:09:13 -0800 Subject: [PATCH 03/17] corrections suggested in the GitHub PR --- doc/topics/the-event-loop-timers-and-nexttick | 175 +++++++++--------- .../the-event-loop-timers-and-nexttick.md | 174 +++++++++++++++++ 2 files changed, 264 insertions(+), 85 deletions(-) create mode 100644 doc/topics/the-event-loop-timers-and-nexttick.md diff --git a/doc/topics/the-event-loop-timers-and-nexttick b/doc/topics/the-event-loop-timers-and-nexttick index 8001affb643bd1..9f8eafc0ac8e41 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick +++ b/doc/topics/the-event-loop-timers-and-nexttick @@ -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. diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md new file mode 100644 index 00000000000000..38f944bdec3e6c --- /dev/null +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -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 CU 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(): + +`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.) + +## `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 `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 `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 asynchronously +"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: + +```js +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 `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. + +This philosophy can lead to some potentially problematic situations. Take this +snippet for example: + +```js +// 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 `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()` + +We have two calls that are similar as far as users are concerned, but their +names are confusing. + +* `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. `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.* + +## 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. + +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. + From ba9838050aee2f7a299cff6ba06683c339b8c776 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Fri, 29 Jan 2016 11:34:18 -0800 Subject: [PATCH 04/17] removed file without .md extension --- doc/topics/the-event-loop-timers-and-nexttick | 178 ------------------ 1 file changed, 178 deletions(-) delete mode 100644 doc/topics/the-event-loop-timers-and-nexttick diff --git a/doc/topics/the-event-loop-timers-and-nexttick b/doc/topics/the-event-loop-timers-and-nexttick deleted file mode 100644 index a3acdf5185391a..00000000000000 --- a/doc/topics/the-event-loop-timers-and-nexttick +++ /dev/null @@ -1,178 +0,0 @@ -# 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.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 CU 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(): - -`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.) - -## `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 -`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 `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.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: - -```js -function apiCall (arg, callback) { - if (typeof arg !== 'string') - return process.nextTick(callback, - new TypeError('argument should be string')); -} -``` - -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 `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, 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: - -```js -// this has an asynchronous signature, but calls callback synchronously -function someAsyncApiCall (callback) { callback(); }; - -// the callback is called before `someAsyncApiCall` completes. -someAsyncApiCall(() => { - - // 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 `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()` - -We have two calls that are similar as far as users are concerned, but their -names are confusing. - -* `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. `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 (and it leads to code that's compatible with a wider -variety of environments, like browser JS.)* - -## 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. - -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 use - `process.nextTick()` to set a callback to emit the event after the - constructor has finished, which provides the expected results. From 936bf1736b17a967678f4ec79657398074c94dec Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Fri, 29 Jan 2016 12:51:00 -0800 Subject: [PATCH 05/17] add details to explanation of timers --- .../the-event-loop-timers-and-nexttick.md | 159 +++++++++--------- 1 file changed, 84 insertions(+), 75 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 38f944bdec3e6c..867ab27d8dfadf 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -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,30 @@ 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. + +When you create a timer, you make a call to `setTimeout()`. Then, when +the poll phase of the event loop is entered, the number of ms before the +soonest timer is to be called is set as the poll's timeout. Meaning the +poll phase will return after "timeout" ms. After that many +milliseconds, the `poll` phase will return and wrap back around to the +timers phase where those callbacks can be processed. + +Take note that the poll phase can only return while idle; execution of +a callback is allowed to run to completion, and can cause unexpected +delay running the timer. *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 +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: @@ -42,20 +49,21 @@ 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()`: -`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. +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()` @@ -65,61 +73,59 @@ 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.) +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.) ## `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 `process.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 `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 asynchronously -"starve" your I/O by making recursive `process.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: ```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 `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. +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: ```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,17 +137,17 @@ 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 `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. +`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 `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()` @@ -149,26 +155,29 @@ We have two calls that are similar as far as users are concerned, but their names are confusing. * `process.nextTick()` fires immediately on the same phase -* `setImmediate()` fires on the following iteration or 'tick' of the event loop +* `setImmediate()` fires on the following iteration or 'tick' of the event + loop -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. +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 `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. From 35cf726b79bb2e22a2d0a632fb4d899a532d13f4 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Wed, 17 Feb 2016 14:41:23 -0800 Subject: [PATCH 06/17] update to address comments on PR --- .../the-event-loop-timers-and-nexttick.md | 273 ++++++++++++++---- 1 file changed, 224 insertions(+), 49 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 867ab27d8dfadf..b2e5b1799abbf7 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -1,4 +1,23 @@ -# Overview of the Event Loop, Timers, and `process.nextTick()` +# The Node.js Event Loop, Timers, and `process.nextTick()` + +## What is the Event Loop? + +The event loop is what allows Node.js to perform non-blocking I/O +operations — despite the fact that JavaScript is single-threaded — by +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 +this in further detail later in this topic. + +## Event Loop Explained + +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. The Following diagram shows a simplified overview of the event loop's order of operations. @@ -18,54 +37,146 @@ order of operations. *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.js actually uses are these four.* +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 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 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. + +_**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 these four._ + + +## Phases Overview: + +* `timers`: this phase executes callbacks scheduled by `setTimeout()` + and `setInterval()`. +* `pending callbacks`: this phase executes callbacks for specific types of TCP + errors, for example. +* `poll`: this is the phase in which the event loop either processes + its queue of callbacks, or sits and waits for new callbacks for incoming + connections, completion of async file I/O, DNS operations, etc. to be added + to the queue. Ideally, most scripts spend most of their time here. If not, + it might be time consider refactoring or scaling to more processes. +* `setImmediate`: This phase allows a person to execute callbacks + immediately after the `poll` phase has completed. + +## Phases in Detail + +### timers + +Contrary to what a person might expect, a timer specifies the **threshold** +_after which_ the provided callback _may be executed_ rather than the +**exact** time a person _wants it to be executed_. + +_**Note**: Technically, the [`poll` phase](#poll) controls when timers are +executed. While waiting for the minimum threshold specified when scheduling +the timer, the `poll` phase will iterate through its queue of callbacks as +usual. Collectively, these callbacks **may or may not** take longer to +complete than the minimum threshold specified by the timer. The actual amount +of time depends on how many callbacks end up being executed and how long they +take._ + +For example, say you schedule a timeout to execute after a 100 ms threshold, +then your script starts asynchronously reading a file which takes 95 ms: + +```js + +var fs = require('fs'); + +function someAsyncOperation (callback) { + + // let's assume this takes 95ms to complete + fs.readFile('/path/to/file', callback); + +} + +var timeoutScheduled = Date.now(); -## timers +setTimeout(function () { -This phase executes callbacks scheduled by `setTimeout()` and `setInterval()`. + var delay = Date.now() - timeoutScheduled; -When you create a timer, you make a call to `setTimeout()`. Then, when -the poll phase of the event loop is entered, the number of ms before the -soonest timer is to be called is set as the poll's timeout. Meaning the -poll phase will return after "timeout" ms. After that many -milliseconds, the `poll` phase will return and wrap back around to the -timers phase where those callbacks can be processed. + console.log(delay + "ms have passed since I was scheduled"); +}, 100); -Take note that the poll phase can only return while idle; execution of -a callback is allowed to run to completion, and can cause unexpected -delay running the timer. -*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.* +// do someAsyncOperation which takes 95 ms to complete +someAsyncOperation(function () { -## pending callbacks: + var startCallback = Date.now(); -This phase executes callbacks for specific types of TCP errors, for example. + // do something that will take 10ms... + while (Date.now() - startCallback < 10) { + ; // do nothing + } -## poll: +}); +``` + +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 +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 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 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 more events. + +### pending 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 `pending callbacks` phase. -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. +### poll: -## `setImmediate()`: +When the event loop enters the `poll` phase _and there are no timers +scheduled_, one of two things will happend: + +* _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**_, the event loop will wait for a +callback +to be added to the queue, then execute it immediately. + +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 those timers' +callbacks. + +### `setImmediate()`: `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. +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. +However, after a callback has been scheduled with `setImmediate()`, +then the `poll` phase becomes idle, it will end and continue to the +`setImmediate` phase rather than waiting for `poll` events. -### `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 @@ -83,14 +194,15 @@ microsecond scale (1 ms = 1000 µs.) 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. +`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. 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. +calls.** which prevents the event loop from reaching the `poll` phase. ### Why would that be allowed? @@ -112,10 +224,13 @@ 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, 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. +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 unwind then immediately execute the +provided callback which allows a person to make recursive calls to nextTick +without reaching a `RangeError: Maximum call stack size exceeded from v8`. This philosophy can lead to some potentially problematic situations. Take this snippet for example: @@ -169,15 +284,75 @@ names themselves won't change. to reason about (and it leads to code that's compatible with a wider variety of environments, like browser JS.)* -## Two reasons to use `process.nextTick()`: +## Why use `process.nextTick()`? + +There are two main reasons: 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 use - `process.nextTick()` to set a callback to emit the event after the - constructor has finished, which provides the expected results. +perhaps try the request again before the event loop continues. + +2. At times it's necessary to allow a callback to run after the call +stack has unwound but before the event loop continues. + +One example is to match the user's expectations. Simple example: + +```js +var server = net.createServer(); +server.on('connection', function(conn) { }); + +server.listen(8080); +server.on('listening', function() { }); +``` + +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 allowing +the connection event to be fired before the listening event. + +Another example is running a function constructor that was to, say, +inherit from `EventEmitter` and it wanted to call an event within the +constructor: + +```js +const EventEmitter = require('events'); +const util = require('util'); + +function MyEmitter() { + EventEmitter.call(this); + this.emit('event'); +} +util.inherits(MyEmitter, EventEmitter); + +const myEmitter = new MyEmitter(); +myEmitter.on('event', function() { + console.log('an event occurred!'); +}); +``` + +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: + +```js +const EventEmitter = require('events'); +const util = require('util'); + +function MyEmitter() { + EventEmitter.call(this); + + // use nextTick to emit the event once a handler is assigned + process.nextTick(() => { + this.emit('event'); + }.bind(this)); +} +util.inherits(MyEmitter, EventEmitter); + +const myEmitter = new MyEmitter(); +myEmitter.on('event', function() { + console.log('an event occurred!'); +}); +``` From f80d7cc7fe12ffada96c4c2d9fbfb3d1b47b36f1 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Wed, 24 Feb 2016 14:12:24 -0800 Subject: [PATCH 07/17] fixed typo, added example as per @trevnorris --- .../the-event-loop-timers-and-nexttick.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index b2e5b1799abbf7..f105e12200bcd0 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -261,9 +261,26 @@ completion. 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 +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. +A real world example in node would be: + +```js +const server = net.createServer(() => {}).listen(8080); + +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. + +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 any event +handlers they want. + ## process.nextTick() vs `setImmediate()` We have two calls that are similar as far as users are concerned, but their From 254694bc5f73356e96f100d5bda301a54af1940b Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Wed, 24 Feb 2016 15:25:49 -0800 Subject: [PATCH 08/17] fixed styling nits identified by @mscdex --- doc/topics/the-event-loop-timers-and-nexttick.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index f105e12200bcd0..40a1029169ba0b 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -164,7 +164,7 @@ time thresholds have been reached_. If one or more timers are ready, the event loop will wrap back to the timers phase to execute those timers' callbacks. -### `setImmediate()`: +### `setImmediate`: `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 @@ -281,7 +281,7 @@ 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 any event handlers they want. -## 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. @@ -322,7 +322,7 @@ server.on('listening', function() { }); ``` 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 +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 allowing From 45fb2fe49abe730996fc1a3787064db1e0a99575 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Fri, 26 Feb 2016 14:52:08 -0800 Subject: [PATCH 09/17] fixes suggested by @silverwind and @fishrock123 --- .../the-event-loop-timers-and-nexttick.md | 142 +++++++++--------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 40a1029169ba0b..836d3e43eece2e 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -4,22 +4,22 @@ The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by -offloading operations to the system kernel whenever possible. +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 +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 -this in further detail later in this topic. +may added to the `poll` queue to eventually be executed. We'll explain +this in further detail later in this topic. ## Event Loop Explained 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. +then begins processing the event loop. -The Following diagram shows a simplified overview of the event loop's +The following diagram shows a simplified overview of the event loop's order of operations. ┌───────────────────────┐ @@ -29,47 +29,47 @@ order of operations. │ │ pending callbacks │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ - │ │ poll │<─────│ connections, │ + │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ - └──│ setImmediate │ + └──┤ setImmediate │ └───────────────────────┘ *note: each box will be referred to as a "phase" of the event loop.* -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 +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 queue has been exhausted or the callback -limit is reached, the event loop will move to the next phase, and so on. +of callbacks have 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 can be -queued while polling events are being processed. As a result, long running +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 +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, +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 these four._ ## Phases Overview: * `timers`: this phase executes callbacks scheduled by `setTimeout()` - and `setInterval()`. + and `setInterval()`. * `pending callbacks`: this phase executes callbacks for specific types of TCP - errors, for example. + errors, for example. * `poll`: this is the phase in which the event loop either processes - its queue of callbacks, or sits and waits for new callbacks for incoming - connections, completion of async file I/O, DNS operations, etc. to be added - to the queue. Ideally, most scripts spend most of their time here. If not, - it might be time consider refactoring or scaling to more processes. + its queue of callbacks, or sits and waits for new callbacks for incoming + connections, completion of async file I/O, DNS operations, etc. to be added + to the queue. Ideally, most scripts spend most of their time here. If not, + it might be time consider refactoring or scaling to more processes. * `setImmediate`: This phase allows a person to execute callbacks - immediately after the `poll` phase has completed. + immediately after the `poll` phase has completed. ## Phases in Detail @@ -82,8 +82,8 @@ _after which_ the provided callback _may be executed_ rather than the _**Note**: Technically, the [`poll` phase](#poll) controls when timers are executed. While waiting for the minimum threshold specified when scheduling the timer, the `poll` phase will iterate through its queue of callbacks as -usual. Collectively, these callbacks **may or may not** take longer to -complete than the minimum threshold specified by the timer. The actual amount +usual. Collectively, these callbacks **may or may not** take longer to +complete than the minimum threshold specified by the timer. The actual amount of time depends on how many callbacks end up being executed and how long they take._ @@ -126,27 +126,26 @@ 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 -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 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 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 be -105ms. +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 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 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 more events. +a hard maximum (system dependent) before it stops `poll`ing for more events. ### pending 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 +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 `pending callbacks` phase. -### poll: +### poll: When the event loop enters the `poll` phase _and there are no timers scheduled_, one of two things will happend: @@ -160,21 +159,21 @@ callback to be added to the queue, then execute it immediately. 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 those timers' +time thresholds have been reached_. If one or more timers are ready, the +event loop will wrap back to the timers phase to execute those timers' callbacks. ### `setImmediate`: `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 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()`, -then the `poll` phase becomes idle, it will end and continue to the -`setImmediate` phase rather than waiting for `poll` events. +However, after a callback has been scheduled with `setImmediate()`, then the +`poll` phase becomes idle, it will end and continue to the `setImmediate` +phase rather than waiting for `poll` events. ## `setImmediate()` vs `setTimeout()` @@ -193,8 +192,8 @@ microsecond scale (1 ms = 1000 µs.) ### 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 -`process.nextTick()` is not technically part of the event loop. Instead, 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, the nextTickQueue will be processed after the current operation completes, regardless of the current `phase` of the event loop. @@ -202,13 +201,13 @@ 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. +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.js? Part of it is a design +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: +have to be. Take this code snippet for example: ```js function apiCall (arg, callback) { @@ -219,20 +218,20 @@ function apiCall (arg, callback) { ``` 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 +error to the callback. The API updated fairly recently to allow passing 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 but only *after* we have -allowed the rest of the user's code to execute. By using `process.nextTick()` +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 +user's code and *before* the event loop is allowed to proceed. To acheive 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 `RangeError: Maximum call stack size exceeded from v8`. -This philosophy can lead to some potentially problematic situations. Take +This philosophy can lead to some potentially problematic situations. Take this snippet for example: ```js @@ -251,17 +250,17 @@ var bar = 1; ``` The user defines `someAsyncApiCall()` to have an asynchronous signature, -actually operates synchronously. When it is called, the callback provided to +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 +`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 `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 +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. A real world example in node would be: @@ -273,12 +272,11 @@ 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. +callback could be called immediately. Problem is that the `.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 any event +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 any event handlers they want. ## `process.nextTick()` vs `setImmediate()` @@ -288,13 +286,13 @@ names are confusing. * `process.nextTick()` fires immediately on the same phase * `setImmediate()` fires on the following iteration or 'tick' of the event - loop + loop -In essence, the names should be swapped. `process.nextTick()` fires more +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 +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 @@ -308,8 +306,8 @@ There are two main reasons: 1. Allow users to handle errors, cleanup any then unneeded resources, or perhaps try the request again before the event loop continues. -2. At times it's necessary to allow a callback to run after the call -stack has unwound but before the event loop continues. +2. At times it's necessary to allow a callback to run after the call stack has +unwound but before the event loop continues. One example is to match the user's expectations. Simple example: @@ -321,11 +319,11 @@ server.listen(8080); server.on('listening', function() { }); ``` -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 allowing +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 allowing the connection event to be fired before the listening event. Another example is running a function constructor that was to, say, @@ -349,8 +347,8 @@ myEmitter.on('event', function() { ``` 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, +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: From d6d76f5b1a43806c8499f8ceebc86dc59d067d08 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Fri, 25 Mar 2016 14:10:07 -0700 Subject: [PATCH 10/17] addressed comments made on GH issue --- .../the-event-loop-timers-and-nexttick.md | 107 ++++++++++++++---- 1 file changed, 87 insertions(+), 20 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 836d3e43eece2e..3046a7a3654c76 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -66,26 +66,25 @@ but the ones we care about — ones that Node.js actually uses are these four._ * `poll`: this is the phase in which the event loop either processes its queue of callbacks, or sits and waits for new callbacks for incoming connections, completion of async file I/O, DNS operations, etc. to be added - to the queue. Ideally, most scripts spend most of their time here. If not, - it might be time consider refactoring or scaling to more processes. + to the queue. * `setImmediate`: This phase allows a person to execute callbacks - immediately after the `poll` phase has completed. + 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 `setImmediate` phase rather than waiting. ## Phases in Detail ### timers -Contrary to what a person might expect, a timer specifies the **threshold** -_after which_ the provided callback _may be executed_ rather than the -**exact** time a person _wants it to be executed_. +A timer specifies the **threshold** _after which_ a provided callback +_may be executed_ rather than the **exact** time a person _wants it to +be executed_. Timers callbacks will run as early as they can be +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 are -executed. While waiting for the minimum threshold specified when scheduling -the timer, the `poll` phase will iterate through its queue of callbacks as -usual. Collectively, these callbacks **may or may not** take longer to -complete than the minimum threshold specified by the timer. The actual amount -of time depends on how many callbacks end up being executed and how long they -take._ +executed._ For example, say you schedule a timeout to execute after a 100 ms threshold, then your script starts asynchronously reading a file which takes 95 ms: @@ -147,16 +146,29 @@ error. This will be queued to execute in the `pending callbacks` phase. ### poll: +The poll phase has two main functions: + +1. Executing scripts for timers who's threshold has elapsed, then +2. Processing events in the `poll` queue. + + When the event loop enters the `poll` phase _and there are no timers scheduled_, one of two things will happend: -* _if the `poll` queue **is not empty**_, the event loop will iterate through +* _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**_, the event loop will wait for a -callback -to be added to the queue, then execute it immediately. +* _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 `setImmediate` 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. 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 @@ -177,9 +189,64 @@ phase rather than waiting for `poll` events. ## `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. +`setTimeout()` is designed to execute a script after a minumum threshold +in ms has elapsed, whereas `setImmediate()` is designed to execute a +script once the current `poll` phase completes. + +How quickly a `setImmediate()` callback is executed depends on how it is +called and how quickly the event loop can be processed unlike timers +whose callback won't fire until at least its threshold has elapsed. + +For example, if we run the following script which is not within a I/O +cycle (i.e. the main module), the order in which the two functions are +executed is non-deterministic as it is based upon how fast your process +go, which is impacted by other programs running on your machine: + + +```js +// timeout_vs_immediate.js +setTimeout(function timeout () { + console.log('timeout'); +},0); + +setImmediate(function immediate () { + console.log('immediate'); +}); +``` + + $ node timeout_vs_immediate.js + timeout + immediate + + $ node timeout_vs_immediate.js + immediate + timeout + + +If you move the two calls within an I/O cycle, the immediate callback +is always executed first: + +```js +// timeout_vs_immediate.js +var fs = require('fs') + +fs.readFile(__filename, () => { + setTimeout(() => { + console.log('timeout') + }, 0) + setImmediate(() => { + console.log('immediate') + }) +}) +``` + + $ node timeout_vs_immediate.js + immediate + timeout + + $ node timeout_vs_immediate.js + immediate + timeout 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 @@ -360,7 +427,7 @@ function MyEmitter() { EventEmitter.call(this); // use nextTick to emit the event once a handler is assigned - process.nextTick(() => { + process.nextTick(function () { this.emit('event'); }.bind(this)); } From c133cafedc0d933caced07509286167c32217bc6 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Fri, 25 Mar 2016 14:29:13 -0700 Subject: [PATCH 11/17] updated `setImmediate()` vs `setTimeout()` section --- doc/topics/the-event-loop-timers-and-nexttick.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 3046a7a3654c76..4a638e3fd0bf36 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -248,11 +248,9 @@ fs.readFile(__filename, () => { immediate timeout -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.) +The main advantage to using `setImmediate()` over `setTimeout()` is that +`setImmediate` has high probability of running before setTimeout when both +are called within the same I/O cycle. ## `process.nextTick()`: From f4251645bbca58181c80453c0fc021889cfa27e0 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Tue, 29 Mar 2016 12:09:14 -0700 Subject: [PATCH 12/17] update overview, phase detail headings, wrap at 72 --- .../the-event-loop-timers-and-nexttick.md | 294 ++++++++++-------- 1 file changed, 160 insertions(+), 134 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 4a638e3fd0bf36..91ab606c1b66fc 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -6,18 +6,18 @@ The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by 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 +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 -this in further detail later in this topic. +this in further detail later in this topic. ## Event Loop Explained -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. +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. The following diagram shows a simplified overview of the event loop's order of operations. @@ -26,51 +26,58 @@ order of operations. ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ - │ │ pending callbacks │ + │ │ i/o callbacks │ + │ └──────────┬────────────┘ + │ ┌──────────┴────────────┐ + │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ - │ ┌──────────┴────────────┐ └───────────────┘ - └──┤ setImmediate │ - └───────────────────────┘ + │ ┌──────────┴────────────┐ └───────────────┘ + │ │ check │ + │ └──────────┬────────────┘ + │ ┌──────────┴────────────┐ + │ │ close callbacks │ + │ └──────────┬────────────┘ + │ ┌──────────┴────────────┐ + └──┤ timers │ + └───────────────────────┘ *note: each box will be referred to as a "phase" of the event loop.* 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 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 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. +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 +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 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. _**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 these four._ +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 these four._ ## Phases Overview: * `timers`: this phase executes callbacks scheduled by `setTimeout()` and `setInterval()`. -* `pending callbacks`: this phase executes callbacks for specific types of TCP - errors, for example. -* `poll`: this is the phase in which the event loop either processes - its queue of callbacks, or sits and waits for new callbacks for incoming - connections, completion of async file I/O, DNS operations, etc. to be added - to the queue. -* `setImmediate`: 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 `setImmediate` phase rather than waiting. +* `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', ...) +* `timers`: indeed, again ## Phases in Detail @@ -83,11 +90,12 @@ 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 are -executed._ +_**Note**: Technically, the [`poll` phase](#poll) controls when timers +are executed._ -For example, say you schedule a timeout to execute after a 100 ms threshold, -then your script starts asynchronously reading a file which takes 95 ms: +For example, say you schedule a timeout to execute after a 100 ms +threshold, then your script starts asynchronously reading a file which +takes 95 ms: ```js @@ -129,20 +137,22 @@ 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 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 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 be 105ms. +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 +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 +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 more events. +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 +more events. -### pending callbacks: +### i/o callbakcs: -This phase executes callbacks for some system operations such as types of TCP -errors. For example if a TCP socket receives `ECONNREFUSED` when +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 `pending callbacks` phase. +error. This will be queued to execute in the `i/o callbakcs` phase. ### poll: @@ -153,39 +163,52 @@ The poll phase has two main functions: When the event loop enters the `poll` phase _and there are no timers -scheduled_, one of two things will happend: +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 is reached. +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 happen: * If scripts have been scheduled by `setImmediate()`, the event loop - will end the `poll` phase and continue to the `setImmediate` 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. -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 those timers' -callbacks. +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 +those timers' callbacks. + +### `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. + +`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()`, +then the `poll` phase becomes idle, it will end and continue to the +`check` phase rather than waiting for `poll` events. + +### `close callbacks: + + -### `setImmediate`: +### `timers` again -`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()`, then the -`poll` phase becomes idle, it will end and continue to the `setImmediate` -phase rather than waiting for `poll` events. ## `setImmediate()` vs `setTimeout()` @@ -249,8 +272,8 @@ fs.readFile(__filename, () => { timeout The main advantage to using `setImmediate()` over `setTimeout()` is that -`setImmediate` has high probability of running before setTimeout when both -are called within the same I/O cycle. +`setImmediate()` has high probability of running before setTimeout when +both are called within the same I/O cycle. ## `process.nextTick()`: @@ -258,21 +281,22 @@ are called within the same I/O cycle. 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, the -nextTickQueue will be processed after the current operation completes, -regardless of the current `phase` of the event loop. +`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. -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. +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.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: +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: ```js function apiCall (arg, callback) { @@ -282,22 +306,23 @@ function apiCall (arg, callback) { } ``` -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 `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. +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 `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 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 unwind then immediately execute the -provided callback which allows a person to make recursive calls to nextTick -without reaching a `RangeError: Maximum call stack size exceeded from v8`. +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 +unwind then immediately execute the provided callback which allows a +person to make recursive calls to nextTick without reaching a +`RangeError: Maximum call stack size exceeded from v8`. -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: ```js // this has an asynchronous signature, but calls callback synchronously @@ -315,18 +340,19 @@ 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 `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. +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 `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. A real world example in node would be: @@ -336,33 +362,33 @@ const server = net.createServer(() => {}).listen(8080); 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. +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. -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 any event -handlers they want. +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 +any event handlers they want. ## `process.nextTick()` vs `setImmediate()` -We have two calls that are similar as far as users are concerned, but their -names are confusing. +We have two calls that are similar as far as users are concerned, but +their names are confusing. * `process.nextTick()` fires immediately on the same phase -* `setImmediate()` fires on the following iteration or 'tick' of the event - loop +* `setImmediate()` fires on the following iteration or 'tick' of the +event loop 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. +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 (and it leads to code that's compatible with a wider -variety of environments, like browser JS.)* +*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.)* ## Why use `process.nextTick()`? @@ -371,8 +397,8 @@ There are two main reasons: 1. Allow users to handle errors, cleanup any then unneeded resources, or perhaps try the request again before the event loop continues. -2. At times it's necessary to allow a callback to run after the call stack has -unwound but before the event loop continues. +2. At times it's necessary to allow a callback to run after the call +stack has unwound but before the event loop continues. One example is to match the user's expectations. Simple example: @@ -384,12 +410,12 @@ server.listen(8080); server.on('listening', function() { }); ``` -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 allowing -the connection event to be fired before the listening event. +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 +allowing the connection event to be fired before the listening event. Another example is running a function constructor that was to, say, inherit from `EventEmitter` and it wanted to call an event within the From 1bd3e6c09e3ea30e62349351c08b8ec98cd80ec2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 31 Mar 2016 10:50:09 +0200 Subject: [PATCH 13/17] docs: minor nits on the libuv phases. --- .../the-event-loop-timers-and-nexttick.md | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 91ab606c1b66fc..a2fbe28a00d472 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -26,7 +26,7 @@ order of operations. ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ - │ │ i/o callbacks │ + │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ @@ -72,13 +72,17 @@ actually uses are these four._ * `timers`: this phase executes callbacks scheduled by `setTimeout()` 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 -* `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 * `close callbacks`: e.g socket.on('close', ...) * `timers`: indeed, again +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. + ## Phases in Detail ### timers @@ -147,12 +151,12 @@ 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 more events. -### i/o callbakcs: +### 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 callbakcs` phase. +error. This will be queued to execute in the `I/O callbacks` phase. ### poll: @@ -202,13 +206,17 @@ 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. -### `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 +emitted via `process.nextTick()`. ### `timers` again - +Timers are checked twice to reduce the drift between the expected time +and the actual execution time, as more code is executed between two +loop runs. ## `setImmediate()` vs `setTimeout()` @@ -271,9 +279,9 @@ fs.readFile(__filename, () => { immediate timeout -The main advantage to using `setImmediate()` over `setTimeout()` is that -`setImmediate()` has high probability of running before setTimeout when -both are called within the same I/O cycle. +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 on how many timers are present. ## `process.nextTick()`: From 7574d4b18f32efd1db19ac7da539e5d57467ee7e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 31 Mar 2016 11:17:42 +0200 Subject: [PATCH 14/17] Removed second timer phase. --- doc/topics/the-event-loop-timers-and-nexttick.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index a2fbe28a00d472..f92b798245f279 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -38,10 +38,7 @@ order of operations. │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ - │ │ close callbacks │ - │ └──────────┬────────────┘ - │ ┌──────────┴────────────┐ - └──┤ timers │ + └──┤ close callbacks │ └───────────────────────┘ *note: each box will be referred to as a "phase" of the event loop.* @@ -77,7 +74,6 @@ actually uses are these four._ * `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', ...) -* `timers`: indeed, again 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 @@ -212,12 +208,6 @@ 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 emitted via `process.nextTick()`. -### `timers` again - -Timers are checked twice to reduce the drift between the expected time -and the actual execution time, as more code is executed between two -loop runs. - ## `setImmediate()` vs `setTimeout()` `setTimeout()` is designed to execute a script after a minumum threshold From d82a7f11fd9421b6403bf46d3ba607874d99bf87 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Thu, 31 Mar 2016 13:09:29 -0700 Subject: [PATCH 15/17] fix nits presented by @ajafff - [x] https://github.com/nodejs/node/pull/4936#discussion_r58081086 - [x] https://github.com/nodejs/node/pull/4936#discussion_r58081459 --- doc/topics/the-event-loop-timers-and-nexttick.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index f92b798245f279..fe05143b1b6355 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -62,7 +62,7 @@ _**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 these four._ +actually uses are those above._ ## Phases Overview: @@ -202,7 +202,7 @@ 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. -### `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 From 1dc26f62599b8a5adc9b91067dadb2cabba4e87d Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Thu, 31 Mar 2016 13:56:13 -0700 Subject: [PATCH 16/17] fix backticks on line 205 --- doc/topics/the-event-loop-timers-and-nexttick.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index fe05143b1b6355..2460250ff079b7 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -202,7 +202,7 @@ 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. -### `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 From 82d0fb8d5cf87d7234ab2f88e36edee3805966e6 Mon Sep 17 00:00:00 2001 From: Jeff Harris Date: Thu, 7 Apr 2016 15:31:39 -0700 Subject: [PATCH 17/17] Improve wording `setTimeout()` vs `setImmediate()` --- .../the-event-loop-timers-and-nexttick.md | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/topics/the-event-loop-timers-and-nexttick.md b/doc/topics/the-event-loop-timers-and-nexttick.md index 2460250ff079b7..fe58298d320048 100644 --- a/doc/topics/the-event-loop-timers-and-nexttick.md +++ b/doc/topics/the-event-loop-timers-and-nexttick.md @@ -210,18 +210,23 @@ emitted via `process.nextTick()`. ## `setImmediate()` vs `setTimeout()` -`setTimeout()` is designed to execute a script after a minumum threshold -in ms has elapsed, whereas `setImmediate()` is designed to execute a -script once the current `poll` phase completes. +`setImmediate` and `setTimeout()` are similar, but behave in different +ways depending on when they are called. -How quickly a `setImmediate()` callback is executed depends on how it is -called and how quickly the event loop can be processed unlike timers -whose callback won't fire until at least its threshold has elapsed. +* `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. + +The order in which they are execute varies depending on the context in +which they are called. If both are called in the main module then you +are bound to how fast your process go, which is impacted by other +programs running on your machine. For example, if we run the following script which is not within a I/O cycle (i.e. the main module), the order in which the two functions are executed is non-deterministic as it is based upon how fast your process -go, which is impacted by other programs running on your machine: +goes (which is impacted by other programs running on your machine): ```js @@ -244,8 +249,8 @@ setImmediate(function immediate () { timeout -If you move the two calls within an I/O cycle, the immediate callback -is always executed first: +However, if you move the two calls within an I/O cycle, the immediate +callback is always executed first: ```js // timeout_vs_immediate.js @@ -271,7 +276,7 @@ fs.readFile(__filename, () => { 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 on how many timers are present. +within an I/O cycle, independently of how many timers are present. ## `process.nextTick()`: