分享更有价值
被信任是一种快乐

Node事件循环机制是什么

文章页正文上

这篇文章主要介绍“Node事件循环机制是什么”,在日常操作中,相信很多人在Node事件循环机制是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Node事件循环机制是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧! 虽然js可以在浏览器中执行又可以在node中执行,但是它们的事件循环机制并不是一样的。并且有很大的区别。在说Node事件循环机制之前,我们先来讨论两个问题学习事件循环可以让开发者明白JavaScript的运行机制是怎么样的。事件循环机制用于管理异步API的回调函数什么时候回到主线程中执行。Node.js采用的是异步IO模型。同步API在主线程中执行,异步API在底层的C++维护的线程中执行,异步API的回调函数也会在主线程中执行。【相关教程推荐:nodejs视频教程、编程教学】在Javascript应用运行时,众多异步API的回调函数什么时候能回到主线程中调用呢?这就是事件环环机制做的事情,管理异步API的回调函数什么时候回到主线程中执行。在Node中的事件循环分为六个阶段。在事件循环中的每个阶段都有一个队列,存储要执行的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空。这六个阶段都存储着异步回调函数,所以还是遵循先执行主线程同步代码,当同步代码执行完后再来轮询这六个阶段。接下来,我们来详细看看这六个阶段里面存储的都是什么Timers:用于存储定时器的回调函数(setlnterval,setTimeout)。Pendingcallbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用。idle,prepare:系统内部使用。(这个我们程序员不用管)Poll:存储1/O操作的回调函数队列,比如文件读写操作的回调函数。在这个阶段需要特别注意,如果事件队列中有回调函数,则执行它们直到清空队列
,否则事件循环将在此阶段停留一段时间以等待新的回调函数进入。但是对于这个等待并不是一定的,而是取决于以下两个条件:如果setlmmediate队列(check阶段)中存在要执行的调函数。这种情况就不会等待。timers队列中存在要执行的回调函数,在这种情况下也不会等待。事件循环将移至check阶段,然后移至Closingcallbacks阶段,并最终从timers阶段进入下一次循环。Check:存储setlmmediate的回调函数。Closingcallbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。跟浏览器中的js一样,node中的异步代码也分为宏任务和微任务,只是它们之间的执行顺序有所区别。我们再来看看Node中都有哪些宏任务和微任务setlntervalsetimeoutsetlmmediateI/OPromise.thenPromise.catchPromise.finallyprocess.nextTick在node中,对于微任务和宏任务的执行顺序到底是怎样的呢?在node中,微任务的回调函数被放置在微任务队列中,宏任务的回调函数被放置在宏任务队列中。微任务优先级高于宏任务。当微任务事件队列中存在可以执行的回调函数时,事件循环在执行完当前阶段的回调函数后会暂停进入事件循环的下免费云主机、域名一个阶段,而会立即进入微任务的事件队列中开始执行回调函数,当微任务队列中的回调函数执行完成后,事件循环才会进入到下一个段开始执行回调函数。对于微任务我们还有个点需要特别注意。那就是虽然nextTick同属于微任务,但是它的优先级是高于其它微任务,在执行微任务时,只有nextlick中的所有回调函数执行完成后才会开始执行其它微任务。总的来说就是当主线程同步代码执行完毕后会优先清空微任务(如果微任务继续产生微任务则会再次清空),然后再到下个事件循环阶段。并且微任务的执行是穿插在事件循环六个阶段中间的,也就是每次事件循环进入下个阶段前会判断微任务队列是否为空,为空才会进入下个阶段,否则先清空微任务队列。下面我们用代码实操来验证前面所说的。在Node应用程序启动后,并不会立即进入事件循环,而是先执行同步代码,从上到下开始执行,同步API立即执行,异步API交给C++维护的线程执行,异步API的回调函数被注册到对应的事件队列中。当所有同步代码执行完成后,才会进入事件循环。

console.log("start");

setTimeout(()=>{
console.log("setTimeout1");
});

setTimeout(()=>{
console.log("setTimeout2");
});

console.log("end");

我们来看执行结果可以看到,先执行同步代码,然后才会进入事件循环执行异步代码,在timers阶段执行两个setTimeout回调。我们知道setTimeout是在timers阶段执行,setImmediate是在check阶段执行。并且事件循环是从timers阶段开始的。所以会先执行setTimeout再执行setImmediate。对于上面的分析一定对吗?我们来看例子

console.log("start");

setTimeout(()=>{
console.log("setTimeout");
});

setImmediate(()=>{
console.log("setImmediate");
});

constsleep=(delay)=>{
conststartTime=+newDate();
while(+newDate()-startTime

执行上面的代码,输出如下先执行setTimeout再执行setImmediate接下来我们来改造下上面的代码,把延迟器去掉,看看会输出什么

setTimeout(()=>{
console.log("setTimeout");
});

setImmediate(()=>{
console.log("setImmediate");
});

我们运行了七次,可以看到其中有两次是先运行的setImmediate怎么回事呢?不是先timers阶段再到check阶段吗?怎么会变呢?其实这就得看进入事件循环的时候,异步回调有没有完全准备好了。对于最开始的例子,因为有2000毫秒的延迟,所以进入事件循环的时候,setTimeout回调是一定准备好了的。所以执行顺序不会变。但是对于这个例子,因为主线程没有同步代码需要执行,所以一开始就进入事件循环,但是在进入事件循环的时候,setTimeout的回调并不是一定完全准备好的,所以就会有先到check阶段执行setImmediate回调函数,再到下一次事件循环的timers阶段来执行setTimeout的回调。那在什么情况下同样的延迟时间,setImmediate回调函数一定会优先于setTimeout的回调呢?其实很简单,只要将这两者放到timers阶段和check阶段之间的Pendingcallbacks、idle,prepare、poll阶段中任意一个阶段就可以了。因为这些阶段完执行完是一定会先到check再到timers阶段的。我们以poll阶段为例,将这两者写在IO操作中。

constfs=require("fs");

fs.readFile("./fstest.js","utf8",(err,data)=>{
setTimeout(()=>{
console.log("setTimeout");
});

setImmediate(()=>{
console.log("setImmediate");
});
});

我们也来执行七次,可以看到,每次都是setImmediate先执行。所以总的来说,同样的延迟时间,setTimeout并不是百分百先于setImmediate执行。主线程同步代码执行完毕后,会先执行微任务再执行宏任务。我们来看下面的例子

console.log("start");

setTimeout(()=>{
console.log("setTimeout");
});

setImmediate(()=>{
console.log("setImmediate");
});

Promise.resolve().then(()=>{
console.log("Promise.resolve");
});

console.log("end");

我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务在微任务中nextTick的优先级是最高的。我们来看下面的例子

console.log("start");

setTimeout(()=>{
console.log("setTimeout");
});

setImmediate(()=>{
console.log("setImmediate");
});

Promise.resolve().then(()=>{
console.log("Promise.resolve");
});

process.nextTick(()=>{
console.log("process.nextTick");
});

console.log("end");

我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

//timers阶段
setTimeout(()=>{
console.log("setTimeout");

Promise.resolve().then(()=>{
console.log("setTimeoutPromise.resolve");
});
});

//check阶段
setImmediate(()=>{
console.log("setImmediate");
Promise.resolve().then(()=>{
console.log("setImmediatePromise.resolve");
});
});

//微任务
Promise.resolve().then(()=>{
console.log("Promise.resolve");
});

//微任务
process.nextTick(()=>{
console.log("process.nextTick");
Promise.resolve().then(()=>{
console.log("nextTickPromise.resolve");
});
});

我们来执行上面的代码可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve。接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve。接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve。这也就印证了微任务会穿插在各个阶段之间运行。到此,关于“Node事件循环机制是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注云技术网站,小编会继续努力为大家带来更多实用的文章!

相关推荐: jquery同胞遍历指的是什么

本文小编为大家详细介绍“jquery同胞遍历指的是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“jquery同胞遍历指的是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。 同胞就是拥有相同的父元素;jquery同胞遍历就是使…

文章页内容下
赞(0) 打赏
版权声明:本站采用知识共享、学习交流,不允许用于商业用途;文章由发布者自行承担一切责任,与本站无关。
文章页正文下
文章页评论上

云服务器、web空间可免费试用

宝塔面板主机、支持php,mysql等,SSL部署;安全高速企业专供99.999%稳定,另有高防主机、不限制内容等类型,具体可咨询QQ:360163164,Tel同微信:18905205712

主机选购导航云服务器试用

登录

找回密码

注册