js的event loop机制,node eventloop
本文带你了解Node.js中的事件循环机制,希望对你有所帮助!
node.js速度课程简介:进入学习
今天,让我们学习nodeJs中的事件循环。对事件循环的理解一直是我的一大难点。希望本研究能够突破这一难点,通过这篇博客加深我对事件循环的理解和印象。
libuv
在学习event-loop之前,先学习一下node的libuv。Libuv负责在不同的操作系统上实现不同的I/O模型,并将不同的实现抽象成可以应用于第三方应用的API。
问题
在正式学习event-loop之前,想一个问题。
setTimeout(()={
console . log( time R1 );
Promise.resolve()。然后(()={
console . log( promise 1 );
});
}, 0);
setTimeout(()={
console . log( timer 2 );
Promise.resolve()。然后(()={
console . log( promise 2 );
});
}, 0);这段代码在浏览器中运行的结果是什么?
在node中运行的结果是什么?
在node8.6之前:
node8.6之后:
为什么会有这样的结果?我们以后再分析!
nodeJs 中的event-loop
首先看一张图:
在图中可以看到六个阶段,分别是:定时器、挂起回调、空闲/准备、轮询、检查和关闭回调。
Timers阶段:主要实现setTimeOut,setInterval的回调。
挂起回调阶段:执行某些系统调用时出错,比如网络通信的错误回调。
空闲/准备阶段:仅在系统内使用(在此阶段我们无法控制干扰)
轮询阶段:获取新的I/O事件,比如获取I/O回调以读取文件。在适合的情况下,nodejs将阻塞在这个阶段
检查阶段:执行setImmediate的回调。
例如,执行sokect的destory,close事件的回调
每个阶段遵循FIFO(先入先出)的规则来执行任务队列中的任务。
在这六个阶段中,我们重点关注timers,poll,check阶段。我们日常开发中的大多数异步任务都是在这三个阶段中处理的。
timers
先说计时器阶段。timers是事件循环的第一个阶段,nodejs会去检查有没有已经过期了的timer,如果有,就将它的回调放入队列中。但是nodejs并不能保证timer在预设事件到了就会立即执行回调,这是因为nodejs对timer的过期检查不一定靠谱,它会受机器上其他运行程序的影响,或者是会遇到当前主线程不空闲的情况。
对于这里的不确定性,官网举了一个例子:
首先声明一个setTimeOut,然后从外部读取一个文件。当文件读取操作超过定时器时间时,文件读取操作会延迟定时器的回调。这是主线程没有空闲的情况。
poll
轮询阶段主要执行两件事:
1.在轮询阶段处理任务队列。
2.当超时的定时器执行其回调函数时。
在上图中,我们还可以看到:在poll阶段执行完poll任务队列的任务之后,会去检查有无预设的setImmediate,如果有,则进入check阶段,如果没有,则nodejs将会阻塞在这里。
这里我们会有一个问题。如果堵车是在轮询阶段,我们设置的定时器不会执行失败吗?
其实当event-loop阻塞在poll阶段时,nodejs会有一个检查机制,它会去检查timers队列是否为空,如果不为空,则重新进入timers阶段。
check
检查检查阶段主要执行setImmediate的回调函数。
小总结
event-loop的每个阶段都有一个队列,当event-loop达到某个阶段之后,将执行这个阶段的任务队列,直到队列清空或者达到系统规定的最大回调限制之后,才会进入下一个阶段。当所有阶段都执行完成一次之后,称event-loop完成一个tick。
案例
以上,我们已经完成了event-loop的理论部分,但是仅仅靠理论还是无法清晰的理解event-loop。让我们通过几个演示对事件循环有更深的理解!
demo1
const fs=require(fs )
fs.readFile(test.txt ,()={
console.log(readFile )
setTimeout(()={
console . log( settime out );
},0)
setImmediate(()={
console.log(setImmediate )
})
})执行结果:
可见执行结果与我们之前的分析是一致的!
demo2
const fs=require( fs );
const event emitter=require( events )。EventEmitter
设pos=0;
const messenger=new event emitter();
messenger.on(message ,function (msg) {
console . log(pos message: msg );//
});
console . log(pos first );//
process.nextTick(function () {
console . log(pos next tick );//
});
messenger.emit(message , hello!);
fs.stat(__filename,function () {
console . log(pos stat );//
});
setTimeout(function () {
console.log( pos 快速计时器);//
}, 0);
setTimeout(function () {
console.log( pos 长计时器);//
}, 30);
setImmediate(function () {
console . log(pos immediate );//
});
console . log(pos last );//结果:
了解下浏览器和node的event-loop差异在什么地方
在node 8.6 之前:
浏览器中的微任务队列会在每个宏任务完成后执行,而节点中的微任务会在事件周期的各阶段之间执行,即微任务队列会在每个阶段完成后执行。
在8.6之后:
与浏览器节点中微任务的执行一致!
于是,在文章的开头,我们提出的思考问题就有了结果。
关于 process.nextTick()和setImmediate
process.nextTick()
语法:process.nextTick(回调,agrs)
执行时间:
这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()
饥饿现象的解释:
const fs=require( fs );
fs.readFile(test.txt ,(err,msg)={
console . log( readFile );
});
设索引=0;
函数处理程序(){
if (index=30)返回;
指数;
console.log(nextTick 索引);
process.nextTick(处理程序);
}
handler();运行结果:
可以看到,读取文件的回调直到nextTick函数执行30次才会执行!这种现象被称为I/O 饥饿。
当我们将process.nextTick改为setImmediate时
const fs=require( fs );
fs.readFile(test.txt ,(err,msg)={
console . log( readFile );
});
设索引=0;
函数处理程序(){
if (index=30)返回;
指数;
console.log(nextTick 索引);
setImmediate(处理程序);
}
handler();结果:
这两个区别的原因是嵌套调用setImmediate的回调被放在下一个事件循环中!
event-loop核心思维导图
结束语
通过今天的学习,我对事件循环有了更深的理解。那么,下次见。好好学习,天天向上!
有关编程的更多信息,请访问:编程视频!也就是说,上面的文章谈到了Node.js中事件循环机制的细节请关注我们的其他相关文章了解更多!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。