简体   繁体   中英

How predictable is the event loop in Node.JS

If you observe simple client-side JS like this , it's intuitive that if we run into setTimeouts, the time length (in this example's case, 0 ms) determine what is the interval AFTER which the JS runtime actually adds this to the queue as explained here .

In Node.JS, however, if I have some api invocation that is fundamentally async (like fs.readFile()) and in the same codebase if I have a simple setTimeout, my understanding is that the event loop will start reading the file, and if has been read, it goes ahead and queues it up so that the appropriate callback can be fired once the main node thread isn't doing any sequential operations. My question is that does this notion of "adding the setTimeout callback" only AFTER the specific timeout still hold (in contrast to class client-side JS). Specifically, here is an example:

const fs = require('fs');
// Set timeout for 2 secs
setTimeout(function() { 
  console.log('Timeout ran at ' + new Date().toTimeString()); 
}, 2000);
// Read some file
fs.readFile('sampleFile.txt', function(err, data) {
   if(err) {
     throw err;
   }
   console.log(data.toString() + " " + new Date().toTimeString();
}
var start = new Date();
console.log('Enter loop at: '+start.toTimeString());
// run a loop for 4 seconds
var i = 0;
// increment i while (current time < start time + 4000 ms)
while(new Date().getTime() < start.getTime() + 4000) {
  i++;
}
console.log('Exit loop at: ' +new Date().toTimeString() +'. Ran '+i+' iterations.');

The output i get for this is:

Enter loop at: 18:22:14 GMT-0700 (PDT) Exit loop at: 18:22:18 GMT-0700 (PDT). Ran 33980131 iterations. Timeout ran at 18:22:18 GMT-0700 (PDT) sampleFileContents 18:22:18 GMT-0700 (PDT)

Does this mean that somehow the setTimeout callback+message was placed on the event loop queue BEFORE the file could be read completely? This tells me 1 of 3 things: Either the setTimeout callback+message was placed on the queue ready to be fired after 2 secs and the next available tick. OR the time to actually read the sampleFile.txt took more than 2 seconds. OR the sampleFile.txt was read quickly, but somehow it wasn't placed ahead of the setTimeout in the event loop queue.

Am i using the right mental model for think about this? I'm trying to get a deeper understanding of node's internals but without having to dive through the libuv/libeio C-code. I've tried playing around with the timeout, and interestingly enough when I set the timeout to greater than 4000 ms, it appears that in my output, I always print out the sampleFileContents before actually printing what time the timeout ran at.

There is a gotcha. The following lines of code are synchronous and blocking.

// increment i while (current time < start time + 4000 ms)
while(new Date().getTime() < start.getTime() + 4000) {
  i++;
}

Which means that the event loop was hijacked by it and never got to function as you would expect.

The settmeout printing before fileread could mean that at the point of time just before the loop started the timer was already set, but the fileread events were not yet added. I added some more code to verify this idea.

var readStream = fs.createReadStream('sampleFile.txt');

  readStream.on('open', function () {
    console.log('Read started ' + new Date().toTimeString());
  });

  readStream.on('data', function(data) {
  });

  readStream.on('end', function(err) {
   console.log('Read end ' + new Date().toTimeString());
  });   

setTimeout(function() {
  console.log('Timeout ran at ' + new Date().toTimeString());
}, 2000);

var start = new Date();
console.log('Enter loop at: '+start.toTimeString());

var i = 0;
while(new Date().getTime() < start.getTime() + 4000) {
  i++;
}

console.log('Exit loop at: ' +new Date().toTimeString() +'. Ran '+i+' times.');

The output is :

Enter loop at: 22:54:01 GMT+0530 (IST)
Exit loop at: 22:54:05 GMT+0530 (IST). Ran 34893551 times.
Timeout ran at 22:54:05 GMT+0530 (IST)
Read started 22:54:05 GMT+0530 (IST)
Read end 22:54:05 GMT+0530 (IST)

Which proves my theory that they never ran concurrently. As to why this happened I believe that fs events need at least one tick to be queued and sent correctly. But timeouts are added instantly. Since you locked the event loop before the fileread event could be added, it was queued after timeout handler after the loop ended.

You can try running your code without the loop, the output will be

Enter loop at: 22:57:15 GMT+0530 (IST)
Exit loop at: 22:57:15 GMT+0530 (IST). Ran 0 iterations.
 22:57:15 GMT+0530 (IST)
Timeout ran at 22:57:17 GMT+0530 (IST)

if read finishes first.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM