【.com速译】由于JavaScript是单线程的,在浏览器中,为了不阻塞主线程的异步代码处理,等待动作完成,JavaScript使用事件循环不断协调调用堆栈、WebAPI和回调队列之间代码的执行。然而,Node.js本身实现的Node.js事件循环有很多相同的模式,但是由于Node.js不与DOM交互并且可以处理各种输入和输出(I/O),所以它是有区别的以它的工作方式。在本文中,我们将首先了解Node.js事件循环背后的理论,然后探索一些使用setTimeout、setImmediate和process.nextTick的示例。最后,我们将一些工作代码部署到Heroku(这是一种快速部署应用程序的简单方法,请参阅--https://www.heroku.com/)以查看其运行情况。Node.js事件循环通常,Node.js事件循环协调定时器、回调和I/O事件的操作和执行。这就是Node.js在单线程情况下处理异步行为的方式。下面的事件循环图很好地展示了它的执行顺序。如您所见,Node.js事件循环有六个主要阶段,它们是:计时器:由setTimeout和setInterval调度的那些回调在此阶段执行。挂起的回调:那些被推迟到下一个循环迭代的I/O回调在此阶段执行。Idle,prepare:这个阶段只在Node.js内部使用。轮询(Poll):这个阶段用来获取新的I/O事件,执行I/O回调(定时器和setImmediate调度的除外,还有下面说的shutdown回调,毕竟会在其他不同阶段处理)。检查:setImmediate安排的回调都在这个阶段执行。关闭回调(Closecallbacks):该阶段主要执行销毁套接字连接等回调。您可能想知道为什么上述任何阶段都没有提到process.nextTick?嗯,那是因为:作为一种特殊的方法,它在技术上不是Node.js事件循环的一部分。相反,无论什么时候调用process.nextTick方法,都会把自己的回调放入队列中,然后“不管事件循环当前处于哪个阶段,完成后都会处理队列中的各种回调当前操作”(来源:Node.js事件循环文档)。事件循环的场景示例也许你认为上面对Node.js事件循环的每个阶段的解释过于抽象。因此,我在Heroku上创建了一个演示应用程序,其中包含可运行代码片段的各种示例,请参阅--https://nodejs-event-loop-demo.herokuapp.com/。在此应用程序中,单击任何示例按钮都会向服务器发送API请求。另一方面,Node.js会在后端执行所选示例的代码片段,然后通过API将相应的响应返回给前端。您可以从GitHub上的链接查看完整代码。让我们通过下面的例子来更好地理解Node.js事件循环的调用顺序。示例1让我们从以下简单示例开始(如下图所示):示例1-同步代码这里我们有三个功能函数。由于它们是同步的,因此代码从上到下顺序执行。也就是说,如果三个函数的调用顺序是:first,second,third,那么它们的代码也会按照同样的顺序执行:first,second,third。示例2接下来介绍第二个示例中setTimeout的概念(如下图所示):示例2-setTimeout这里我们先调用第一个函数,然后计划延迟0后用setTimeout调用第二个毫秒函数,最后调用第三个函数。那么,这些函数的执行顺序就变成了:第一,第三,第二。你一定很好奇:为什么第二个函数最后执行?让我们了解两个重要的原则。首先,使用带有延迟值的setTimeout方法并不意味着应用程序将在指定的毫秒数后立即执行回调函数。其实这个值代表的是:回调执行前需要经过的最短时间。其次,使用setTimeout为回调设置执行后时间将始终在事件循环的每次迭代期间执行规则。因此,在事件循环的第一次迭代中,第一个函数被执行,第二个函数被“调度”(scheduled),第三个函数再次被执行。但是,在事件循环的第二次迭代中,最小延迟0毫秒是满足的,所以第二个函数是在第二次迭代的“定时器”阶段执行的。例3那么我们在第三个例子中引入setImmediate的概念(如下图所示):例3-setImmediate和setTimeout本例中我们执行第一个函数,第二个函数使用setTimeout延迟0毫秒,然后使用setImmediate来“调度”第三个函数。那么,在代码执行的过程中,就会出现一个问题:哪种排列方式优先?setTimeout还是setImmediate?上面已经讨论了setTimeout的工作机制,下面简单介绍一下setImmediate方法。此方法将在事件循环的下一次迭代的“检查”阶段执行其回调函数。因此,如果在事件循环的第一次迭代期间调用了setImmediate,那么它的回调方法将在事件循环的第二次迭代期间被“调度”并执行。正如您在输出中看到的,在我们的示例中,由于setImmediate调度的回调在setTimeout调度的回调之前执行,因此示例函数的执行顺序为:第一、第三、第二。当然,setImmediate和setTimeout调度的执行实际上取决于被调用方法的上下文。当直接从Node.js脚本的主模块调用这两个方法时,时间取决于进程的性能,因此每次运行脚本时回调可以以不同的顺序执行。但是,当在I/O周期中调用这些方法时,setImmediate回调总是在setTimeout回调之前发生。在我们上面的示例中,由于这些方法是作为响应API端点的一部分调用的,因此setImmediate回调将始终在setTimeout回调之前执行。示例4为了快速检查完整性,我们使用setImmediate和setTimeout构建另一个示例(如下所示)。示例4-再次使用setImmediate和setTimeout在此示例中,我们使用setImmediate调度第一个函数,然后直接执行第二个函数,然后使用setTimeout调度第三个函数,延迟为0毫秒。你可能已经猜到了,上面函数的执行顺序是:second,first,third。在事件循环的第二次迭代中,通过setImmediate将第二个函数安排在I/O周期内执行,然后延迟0毫秒后第三个函数也被执行。示例5下面,我们将process.nextTick方法引入到最后一个示例中(如下图所示)。示例5-process.nextTick在此示例中,我们使用setImmediate安排第一个函数,使用process.nextTick安排第二个函数,使用延迟0毫秒的setTimeout安排第三个函数,最后是第四个函数。那么,代码运行后,整体的调用顺序是:第四,第二,第一,第三。有了前面的基础,我们就很容易理解为什么要先执行第四个函数了。毕竟它是直接调用的,没有任何其他安排方式。process.nextTick方法安排第二个函数先执行,然后是第一个函数。第三个函数最后执行。原因是在同一个I/O周期内,setImmediate安排的回调会先于setTimeout安排的回调执行。那么,为什么process.nextTick安排的第二个函数会先于setImmediate安排的第一个函数执行呢?请不要被这两个方法的名字所误导,不是setImmediate表示回调会立即执行,process.nextTick必须等到下一轮事件循环才执行回调。这里不展开讨论,有兴趣的可以参考https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#process-nexttick-vs-setimmediate。只需要注意:process.nextTick在安排的同一阶段立即执行;而setImmediate的回调则在事件循环的下一次迭代或计时期间执行。小结通过上面的例子,你应该对Node.js的事件循环和setTimeout、setImmediate、process.nextTick等方法有了一定的了解。当然,你不必深究Node.js的内部结构和处理命令的相关操作。我们完全可以把Node.js看成一个黑盒子,轻松利用好Node.js事件循环的调用顺序。要详细了解上述各种示例,您可以通过链接-https://nodejs-event-loop-demo.herokuapp.com/或通过链接-https://github.com/查看其演示应用程序thawkin3/nodejs-event-loop-demo在GitHub上查看他们的代码。您甚至可以参考--https://heroku.com/deploy?template=https://github.com/thawkin3/nodejs-event-loop-demo将代码部署到Heroku。原标题:UnderstandingtheNode.jsEventLoop,作者:TylerHawkins
