nodejs中架构如下图所示,通过v8引擎来执行js代码,通过中间层 libuv 来读写文件系统、网络等做一些操作。
 
 
nodejs中提供阻塞和非阻塞的调用方式,比如fs模块中读取文件,可以根据需要使用 readFile(异步) 或者 readFileSync(同步)。
 
如果使用同步的编程方式,那么后续代码的执行需要等到此次执行结束,程序的运行会被“阻塞”。也可以使用异步的编程方式,后续代码的执行无需等待此次的执行结束,不会“阻塞”程序的运行,但它同样也会存在一个问题,那就是非阻塞式调用需要不断的轮询获取异步调用的执行结果。
 
libuv中的io操作就是使用的”非阻塞式调用“,但它如果不断轮询获取结果这个过程对系统有一定的性能影响,为了将这个影响降低,libuv中将不断轮询的这个过程放到“线程池”当中,当轮询到结果时,将对应的回调和获取的结果放置到 event loop(事件循环)的某一个队列中,事件循环再来进行下一步的操作,通过javascript来执行回调。
 
 
nodejs中的事件循环要比javascript的事件循环(设置一个链接)更为复杂一些,一次循环分为以下几个阶段
* 定时器(timer): 在这个阶段执行 setTimeout、setInterval的回调函数
* 待定回调(pending callbacks):某些系统操作(如 TCP 错误类型)执行回调
* idle, prepare:仅系统内部使用。
* 轮询(poll):检索新的 I/O 事件; 执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外)
* 检测(check):setImmediate() 回调函数在这里执行。
* 关闭的回调函数(close callbacks):一些关闭的回调函数,如:socket.on('close', ...)。
 
nodejs中也和javascript中一样存在着微任务(micro-task)、宏任务(macro-task),两个任务中执行的内容也有一部分的相似性
微任务:promise的then函数的回调、queueMicrotask、process.nextTick
宏任务: setTimeout、setInterval、io事件、setImmediate、close事件

执行顺序也和javascript中一致,先执行主线程的任务,然后接着执微任务,微任务执行完成再执行宏任务,具体的执行顺序如下。

微任务队列
next tick queue:process.nextTick
other tick queue:promise的then函数、queueMicrotask 宏任务队列
timer queue: setTimeout、setInterval
poll queue: io事件
check queue: setImmediate
close queue: close事件

了解完nodejs中事件循环的执行顺序之后,一起来看看下面这道面试题

async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
} async function async2() {
console.log('async2')
} console.log('script start') setTimeout(function () {
console.log('setTimeout0')
}, 0) setTimeout(function () {
console.log('setTimeout2')
}, 300) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick1')); async1(); process.nextTick(() => console.log('nextTick2')); new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
}) console.log('script end')

首先声明了 async1 和 async2函数,只有调用才会被放入调用栈中,所以此时不会执行,往下执行输出 "script start"。

 
继续向下执行,将 "setTimeout0" 放入 宏任务的 timer queue 中,继续执行,"setTimeout2" 需要延迟300ms执行,不放入 timer queue中,接着把 "setImmediate" 放入 check queue 中,把 "nextTick1" 放入 next tick queue 中。
 
此时执行函数 async1,输出 "async1 start",在函数 async1 中执行 async2,输出 "async2",把 "async1 end" 放入 other queue当中,此时函数 async1执行完成,继续向下执行,将"nextTick2"放入next tick queue中,排在"nextTick1"后面,继续输出 "promise1",将"promise3"放在other queue中,排在"async1 end"后面,输出"promise2",最后输出"script end",此时主线程的内容都执行完成。
 
来到微任务队列,微任务队列中先执行 next tick queue 中的内容,依次输出 "nectTick1"、"nectTick2",再执行 other queue 中的内容,依次输出"async1 end"、"promise3"。
 
最后执行宏任务队列中的任务,先执行 timer queue,输出 "setTimeout0",这里没有io事件(poll),往下执行check quue,输出 setImmediate,没有关闭回调函数阶段( close callbasks),一次事件循环结束,来到第二次、第三次事件循环,此时300ms后,"setTimeout2" 被加入到宏任务队列中的 timer queue,事件循环中没有其他的队列,直接输出 "setTimeout2",事件循环结束。
 
简单图示如下
 
再来看另一道面试题
setTimeout(() => {
console.log("setTimeout");
}, 0); setImmediate(() => {
console.log("setImmediate");
});

按照宏任务队列中各任务的执行顺序,setTimeout属于timer queue,setImmediate属于check queue,按理说会先输出setTimeout,但实际情况会是什么样的呢,我们看一下以下输出情况

 
为什么会这样的情况呢,有时候是setTimeout先输出,有时候是setImmediate先输出,原因在于,setTimeout的回调函数虽然是延迟0毫秒执行,但是 setTimeout的准备时间要长于 event loop 的启动时间,当event loop 开始第一次循环的时候,setTimeout 还没有被放入 timer queue 之中,所以 event loop 先执行 check queue 中的 setImmediate,等待第二次循环的时候,timer queue 中才有setTimeout,此时就会出现 setImmediate 先输出的情况。 
 
nodejs中的事件循环机制有一部分和javascript中事件循环是一致的,如果对javascript事件循环机制还不太熟悉,可以看看这一篇文章,javascript事件循环机制及面试题详解
 

nodejs中事件循环机制与面试题详解的更多相关文章

  1. 安卓中的消息循环机制Handler及Looper详解

    我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handl ...

  2. nodejs的事件循环1

    JavaScript的学习零散而庞杂,因此很多时候我们学到了一些东西,但是却没办法感受到自己的进步,甚至过了不久,就把学到的东西给忘了.为了解决自己的这个困扰,在学习的过程中,我一直试图在寻找一条核心 ...

  3. JS基础-事件循环机制

    从一道题浅说 JavaScript 的事件循环 原文链接: https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7 ...

  4. Netty源码解析 -- 事件循环机制实现原理

    本文主要分享Netty中事件循环机制的实现. 源码分析基于Netty 4.1 EventLoop 前面分享服务端和客户端启动过程的文章中说过,Netty通过事件循环机制(EventLoop)处理IO事 ...

  5. JavaScript中的事件循环机制跟函数柯里化

    一.事件循环机制的理解 test();//按秒输出5个5 function test() { for (var i = 0; i < 5; i++) { setTimeout(() => ...

  6. 浏览器中的JavaScript事件循环机制

    浏览器的事件循环机制是HTML中定义的规范. JavaScript有一个主线程和调用栈,所有的任务都会被放到调用栈等待主线程执行. JS调用栈 是一种先进后出的数据结构.当函数被调用时,会被添加到栈中 ...

  7. 浏览器中 JS 的事件循环机制

    目录 事件循环机制 宏任务与微任务 实例分析 参考 1.事件循环机制 浏览器执行JS代码大致可以分为三个步骤,而这三个步骤的往复构成了JS的事件循环机制(如图). 第一步:主线程(JS引擎线程)中执行 ...

  8. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

  9. JS:事件循环机制、调用栈以及任务队列

    点击查看原文 写在前面 js里的事件循环机制十分有趣.从很多面试题也可以看出来,考察简单的setTimeout也就是考察这个机制的. 在之前,我只是简单地认为由于函数执行很快,setTimeout执行 ...

  10. javaScript 事件循环机制

    JavaScript是单线程的编程语言,只能同一时间内做一件事.但是在遇到异步事件的时候,js线程并没有阻塞,还会继续执行,这就是因为JS有事件循环机制. 事件循环流程总结 主线程开始执行一段代码, ...

随机推荐

  1. 2023-03-08:x265的视频编码器,不用ffmpeg,用libx265.dll也行。请用go语言调用libx265.dll,将yuv文件编码成h265文件。

    2023-03-08:x265的视频编码器,不用ffmpeg,用libx265.dll也行.请用go语言调用libx265.dll,将yuv文件编码成h265文件. 答案2023-03-08: 使用 ...

  2. 2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定“认为”关系有传递性,所以A也认为C是红人, 给定一张有向图,方式是给定M个有

    2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定"认为"关系有传递性,所以A也认为C是红 ...

  3. Spring原理(1)——容器

    容器接口 BeanFactory 是ApplicationContext的父接口,所有ApplicationContext的实现都组合了BeanFactory. BeanFactory才是Spring ...

  4. Python Numpy 切片和索引(高级索引、布尔索引、花式索引)

    张量(Tensor).标量(scalar).向量(vector).矩阵(matrix) Python Numpy 切片和索引(高级索引.布尔索引.花式索引) Python NumPy 广播(Broad ...

  5. MAIXIII(爱芯派)的一种配网并安装nmtui的实现方法

    关于一种MAIXIII(爱芯派)的一种配网并安装nmtui的实现方法 特别感谢sipped的大佬鼠以及多位群友这几天提供的帮助与支持! 0.目录 一,MAIXIII简介 二,到手图展示 三,具体操作方 ...

  6. 基于 Dash Bio 的生物信息学数据可视化

    Dash 是用于搭建响应式 Web 应用的 Python 开源库.Dash Bio 是面向生物信息学,且与 Dash 兼容的组件,它可以将生物信息学领域中常见的数据整合到 Dash 应用程序,以实现响 ...

  7. 完成第一个 Vue3.2 项目后,使用体会

    第一次Composition API 在vue3.2中,正式支持了script setup的写法,这样可以大大简化组件的代码量,减少一些重复操作,我认为当你写vue3时,应该把这当作默认写法.在vue ...

  8. DevOps| 研发效能和PMO如何合作共赢?

    项目经理(PMO)对于大组织.跨团队高效协同有着不可替代的作用.跳出组织架构的束缚,横向推动公司级别的大项目向前推进,跟进进展和拿到结果,PMO的小伙伴有着独特的优势. 我之前写过小团队如何高效协作的 ...

  9. Oracle Users表空间重命名

    需求:默认无法直接删除Oracle的users表空间,直接尝试删除会有报错如下: SQL> drop tablespace users including contents and datafi ...

  10. 【python基础】复杂数据类型-字典(遍历)

    一个字典可能只包含几个键值对,也可能包含数百万个键值对,所以Python支持字典遍历.字典可用于以各种方式存储信息,因此有多种遍历字典的方式:可遍历字典的所有键值对.键或值. 1.遍历所有的键值对 其 ...