官术网_书友最值得收藏!

Understanding the event loop

Node processes JavaScript instructions using a single thread. Within your JavaScript program no two operations will ever execute at exactly the same moment, as might happen in a multithreaded environment. Understanding this fact is essential to understanding how a Node program, or process, is designed and runs.

This does not mean that only one thread is being used on the machine hosting this a Node process. Simply writing a callback does not magically create parallelism! Recall Chapter 1, Understanding the Node Environment, and our discussion about the process object—Node's "single thread" simplicity is in fact an abstraction created for the benefit of developers. It is nevertheless crucial to remember that there are many threads running in the background managing I/O (and other things), and these threads unpredictably insert instructions, originally packaged as callbacks, into the single JavaScript thread for processing.

Node executes instructions one by one until there are no further instructions to execute, no more input or output to stream, and no further callbacks waiting to be handled.

Even deferred events (such as timeouts) require an eventual interrupt in the event loop to fulfill their promise.

For example, the following while loop will never terminate:

var stop = false;
setTimeout(function() {
    stop = true;        
}, 1000);

while(stop === false) {};       

Even though one might expect, in approximately one second, the assignment of a Boolean true to the variable stop, tripping the while conditional and interrupting its loop, this will never happen. Why? This while loop starves the event loop by running infinitely, greedily checking and rechecking a value that is never given a chance to change, as the event loop is never given a chance to schedule our timer callback for execution.

As such, programming Node implies programming the event loop. We've previously discussed the event sources that are queued and otherwise arranged and ordered on this event loop—I/O events, timer events, and so on.

When writing non-deterministic code it is imperative that no assumptions about eventual callback orders are made. The abstraction that is Node masks the complexity of the thread pool on which the straightforward main JavaScript thread floats, leading to some surprising results.

We will now refine this general understanding with more information about how, precisely, the callback execution order for each of these types is determined within Node's event loop.

Four sources of truth

We have learned about the four main groups of deferred event sources, whose position and priority on the stack we will now demonstrate:

  • Execution blocks: The blocks of JavaScript code comprising the Node program, being expressions, loops, functions, and so on. This includes EventEmitter events emitted within the current execution context.
  • Timers: Callbacks deferred to sometime in the future specified in milliseconds, such as setTimeout and setInterval.
  • I/O: Prepared callbacks returned to the main thread after being delegated to Node's managed thread pool, such as filesystem calls and network listeners.
  • Deferred execution blocks: Mainly the functions slotted on the stack according to the rules of setImmediate and nextTick.

We have learned how the deferred execution method setImmediate slots its callbacks after I/O callbacks in the event queue, and nextTick slots its callbacks before I/O and timer callbacks.

Tip

A challenge for the reader

After running the following code, what is the expected order of logged messages?

var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
var pos = 0;
var messenger = new EventEmitter();
// Listener for EventEmitter
messenger.on("message", function(msg) {
    console.log(++pos + " MESSAGE: " + msg);
});
// (A) FIRST
console.log(++pos + " FIRST");
//  (B) NEXT
process.nextTick(function() { 
  console.log(++pos + " NEXT") 
}) 
// (C) QUICK TIMER
setTimeout(function() { 
  console.log(++pos + " QUICK TIMER") 
}, 0) 
// (D) LONG TIMER
setTimeout(function() { 
  console.log(++pos + " LONG TIMER") 
}, 10) 
// (E) IMMEDIATE
setImmediate(function() { 
  console.log(++pos + " IMMEDIATE") 
}) 
// (F) MESSAGE HELLO!
messenger.emit("message", "Hello!");
// (G) FIRST STAT
fs.stat(__filename, function() {
    console.log(++pos + " FIRST STAT");
});
// (H) LAST STAT
fs.stat(__filename, function() {
    console.log(++pos + " LAST STAT");
});
// (I) LAST
console.log(++pos + " LAST");

The output of is program is:

  1. FIRST (A).
  2. MESSAGE: Hello! (F).
  3. LAST (I).
  4. NEXT (B).
  5. QUICK TIMER (C).
  6. FIRST STAT (G).
  7. LAST STAT (H).
  8. IMMEDIATE (E).
  9. LONG TIMER (D).

Let's break the preceding code down:

A, F, and I execute in the main program flow and as such they will have the first priority in the main thread (this is obvious; your JavaScript executes its instructions in the order they are written, including the synchronous execution of the emit callback).

With the main call stack exhausted, the event loop is now almost reading to process I/O operations. This is the moment when nextTick requests are honored slotting in at the head of the event queue. This is when B is displayed.

The rest of the order should be clear. Timers and I/O operations will be processed next, (C, G, H) followed by the results of the setImmediate callback (E), always arriving after any I/O and timer responses are executed.

Finally, the long timeout (D) arrives, being a relatively far-future event.

Notice that re-ordering the expressions in this program will not change the output order (outside of possible re-ordering of the STAT results, which only implies that they have been returned from the thread pool in different order, remaining as a group in the correct order as relates to the event queue).

主站蜘蛛池模板: 内黄县| 天水市| 南投市| 石狮市| 泗阳县| 拉孜县| 平利县| 启东市| 丰顺县| 乐亭县| 兴仁县| 弥勒县| 七台河市| 西峡县| 乌什县| 宝鸡市| 赤壁市| 遂溪县| 自治县| 花莲市| 钟祥市| 富顺县| 扬州市| 比如县| 郎溪县| 民乐县| 肇州县| 昌都县| 浙江省| 谢通门县| 桐乡市| 汕尾市| 浦县| 信宜市| 房产| 中超| 田阳县| 汤原县| 屏边| 桐柏县| 三原县|