Node.JS中的事件循环有多可预测性

如果你这样观察简单的客户端JS,直观的是,如果我们运行setTimeouts,时间长度(在本例中为0 ms)决定了JS运行时间实际上将它加到队列中的时间间隔是多less这里 。

然而,在Node.JS中,如果我有一些基本上是asynchronous的api调用(如fs.readFile()),并且在同一个代码库中,如果我有一个简单的setTimeout,我的理解是事件循环将开始读取文件,如果已被读取,则继续并将其排队以便一旦主节点线程不进行任何顺序操作就可以触发适当的callback。 我的问题是,只有在特定的超时后仍然保持(与类客户端JS对比),这个“添加setTimeoutcallback”的概念。 具体来说,这里是一个例子:

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

我得到的这个输出是:

input回路:18:22:14 GMT-0700(PDT)退出回路:18:22:18 GMT-0700(PDT)。 Ran 33980131迭代。 超时时间为18:22:18 GMT-0700(PDT)sampleFileContents 18:22:18 GMT-0700(PDT)

这是否意味着setTimeoutcallback+消息被放置在事件循环队列之前,可以完全读取文件? 这告诉我三件事情之一:setTimeoutcallback+消息被放置在队列准备好在2秒后被触发,并且下一个可用的打勾。 或者实际读取sampleFile.txt的时间超过2秒钟。 或者sampleFile.txt被快速读取,但不知怎的,它并没有放在事件循环队列的setTimeout之前。

我是否用正确的思维模式来思考这个问题? 我试图深入了解node的内部结构,而不必深入libuv / libeio C代码。 我试着玩超时,有趣的是,当我将超时设置为大于4000毫秒,似乎在我的输出中,我总是打印出sampleFileContents之前,实际打印什么时候超时运行。

有一个问题。 以下代码行是同步的和阻塞的。

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

这意味着事件循环被它所劫持,从来没有像你期望的那样工作。

settmeout之前的settmeout打印可能意味着,在循环开始之前的时间点,定时器已经被设置,但是文件读取事件还没有被添加。 我添加了更多的代码来validation这个想法。

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

输出是:

 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) 

这certificate了我的理论,他们从来没有同时运行。 至于为什么发生这种情况,我认为fs事件至less需要一个滴答排队并正确发送。 但超时会立即添加。 由于您在添加fileread事件之前locking了事件循环,所以在循环结束之后它会在超时处理程序之后排队。

你可以尝试运行你的代码而不用循环,输出将会是

 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) 

如果阅读完成。