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

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).

主站蜘蛛池模板: 积石山| 甘南县| 襄汾县| 金昌市| 阳原县| 合川市| 英超| 常熟市| 潮安县| 永靖县| 杭锦后旗| 万源市| 麻栗坡县| 霍州市| 长丰县| 大悟县| 济阳县| 新竹县| 保定市| 洮南市| 太仓市| 滨海县| 伊宁县| 富平县| 增城市| 吉林市| 邯郸市| 灵台县| 灵山县| 陈巴尔虎旗| 大兴区| 永平县| 清水县| 若尔盖县| 星子县| 家居| 文昌市| 龙游县| 开阳县| 称多县| 奇台县|