详解nodejs中的事件循环机制

本篇文章带大家了解一下node中的事件循环机制。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

详解nodejs中的事件循环机制

前端开发离不开JavaScript,Javascript是一种web前端语言,主要用于web开发中,由浏览器解析执行。而js的作用不仅仅局限于前端领域的开发,它同样可以用于服务端开发——nodejs。作为一名有理想抱负的前端,想要拓展视野,掌握一门服务器端开发语言,那么nodejs是非常好的一种选择。

相关推荐:《node》

因为你掌握了js开发方式就很容易上手node,并且npm包管理工具也大大提升了开发体验。nodejs以异步非阻塞I/O工作方式而闻名,其处理机制被称为事件循环。

了解node事件循环机制就能更好的了解node的事件处理方式以及异步事件的执行时机,本文主要讲解一下nodejs的事件循环机制,为后续学习node奠定基础。

一、node VS javascript

前面提到,Javascript是一种web前端语言,主要用于web开发中,由浏览器解析执行,而 node.js 是一个基于 Chrome V8 引擎的JavaScript 运行环境,因此nodejs不是一门语言,不是库,不是框架,而是一个js运行时环境,简单讲node可以解析和执行js代码。以前只有浏览器可以解析执行Js,现在node可以使js完全脱离浏览器来运行。

node.js和浏览器js存在很多区别,比如浏览器中的js包括了ecmascript、BOM、DOM,但是nodejs中的js没有BOM,DOM,只有emcscript。并且node这个js执行环境为js提供了一些服务器级别的操作API,例如:文件读写,网络服务构建,网络通信,http服务器等,这些API大都被包装到核心模块里面了。另外node的事件循环机制和浏览器js的事件循环机制也不一样。

二、JavaScript事件循环

大家对浏览器中的js事件循环已经很清楚了,为了对比这里简单再提一下。

1.png

(转引自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)

js执行时同步和异步任务任务分别进入不同的执行环境,同步任务的进入主线程,即主执行栈,异步任务(ajax请求、settimeout、setinterval、poromise.resolve()等)进入任务队列。不同的异步任务会推入不同的任务队列,比如ajax请求、settimeout、setinterval等这些任务会被推入进宏任务队列(Macro Task),而Promise函数则会被推到微任务队列(Micro Task)。整体的事件循环过程如下:

当同步代码执行完后,主执行栈变空,开始准备执行异步任务。

主线程会检查微任务队列是否为空,如果不为空那么会遍历队列内的所有微任务将其执行完,清空微任务队列,然后再检查宏任务队列。如果微任务队列是空的,直接进入下一步。

主线程遍历宏任务队列,并执行宏任务队列中的第一个宏任务,在执行的过程中如果遇到宏任务或者微任务,则继续将他们推入到对应的任务队列,每次执行完一次宏任务都要遍历执行一下微任务队列,将其清空

执行渲染操作,更新视图

开始下一次的事件循环,重复上述步骤直至两个任务队列清空

为了加深一下影响,举一个小小的,看看以下代码会输出什么:

    var le=Promise.resolve(2);    console.log(le)    console.log('3')    Promise.resolve().then(()=>{    console.log('Promise1')      setTimeout(()=>{        console.log('setTimeout2')    },0)    })    setTimeout(()=>{    console.log('setTimeout1')    Promise.resolve().then(()=>{        console.log('Promise2')        })    },0);

登录后复制

用以上的事件循环过程分析一下:

js主进程执行代码遇到Promise.resolve(2),会立即执行,将2变成一个promise对象,然后console.log(le)将le变量打印出来,打印—–>Promise {: 2};console.log(‘3’),打印—–>3接着往下执行遇到Promise.resolve().then,这是一个异步微任务函数,推到微任务栈下一个函数遇到setTimeout,推到宏任务队列,至此主进程空了检查微任务队列,发现Promise.resolve().then,所以打印—–>promise1,又遇到一个定时器,推至宏任务队列的最后,微任务队列空了检查宏任务队列,取第一个宏任务执行,打印—–>setTimeout1,又遇到 Promise.resolve().then,推至微任务队列再开始下一个宏任务之前,一定会清空微任务,因此打印setTimeout1后,便会检查微任务队列,于是—>promise2接下来又一轮事件循环,取宏任务队列当前的第一个任务执行,于是打印打印—–>setTimeout2,至此宏任务和微任务队列均被清空,事件循环结束

因此输出结果是:Promise {: 2},3,promise1,setTimeout1,promise2,setTimeout2。

浏览器里执行结果如下:

2.png

三、node事件循环

node的事件循环共有六个阶段,在一次事件循环中这六个阶段按顺序会一直循环执行,直至事件处理完成。六个阶段的顺序图如下:

3.png

六个阶段分别是:

timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调I/O callbacks 阶段:执行一些系统操作的回调(比如网络通信的错误回调);idle, prepare 阶段:仅node内部使用,可忽略poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里check 阶段:执行 setImmediate() 的回调close callbacks 阶段:执行 socket 的 close 事件回调,如果一个socket或handle被突然关掉(比如socket.destroy()),close事件将在这个阶段被触发

事件循环中,每当进入某一个阶段,都会从该阶段对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,该阶段就会终止,然后检查NextTick队列和微任务队列,将其清空,之后进入下一个阶段。

这里面比较关键的是poll阶段:

poll队列不为空的时候,事件循环会遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。poll队列为空的时候,就会有两种情况:如果代码中存在setImmediate()回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调;如果不存在setImmediate()回调,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去,如果规定时间内有定时器函数进入队列,则返回到timer阶段,执行定时器回调,否则在poll阶段等待回调进入队列。

同样的举个大大的,看看以下代码会输出什么:

console.log('start')setTimeout(() => {  console.log('timer1')  Promise.resolve().then(function() {    console.log('promise1')  })}, 0)setTimeout(() => {  console.log('timer2')  Promise.resolve().then(function() {    console.log('promise2')  })}, 0)Promise.resolve().then(function() {  console.log('promise3')})console.log('end')

登录后复制

利用node事件循环分析呗:

先执行同步任务,打印start,end进入timer阶段前,清空NextTick和micro队列,所以打印promise3进入timer阶段,打印timer1,并发现有一个微任务,立即执行微任务,打印promise1仍然在timer阶段,执行下个宏任务,打印timer2,同样遇到微任务,立即执行,打印promise2

因此输出顺序是:start,end,promise3,timer1,promise1,timer2,promise2,如果能正确回答出来说明对node的循环机制有了大体的了解,实际node输出结果确实是这样:

4.png

那如下代码会输出什么呢?

process.nextTick(function(){    console.log(7);});new Promise(function(resolve){    console.log(3);    resolve();    console.log(4);}).then(function(){    console.log(5);});process.nextTick(function(){    console.log(8);});

登录后复制

继续分析:

process.nextTick会将任务推进至nextTick队列,promise.then会把任务推至micro队列,上面提到过每次一个宏任务执行完,执行下一个宏任务之前需要清空nextTick队列和micro队列,同样的一个阶段执行完,进入下一个阶段之前也需要nextTick队列和micro队列,并且nextTick队列优先级高于micro队列先执行同步代码,打印3,4执行nextTick队列,打印7,8再执行micro队列,打印5

因此最终输出是:3,4,7,8,5,需要记住,process.nextTick 永远大于 promise.then的优先级

还有一个大家很容易混淆的点就是setTimout和setImmediate的执行时机,根据上面描述的node事件循环机制,setImmediate()应该在check阶段执行 与 而setTimeout在timer阶段执行,理论上setTimout比setImmediate先执行,看下面的代码:

setTimeout(() => console.log(1),0);setImmediate(() => console.log(2));

登录后复制

执行结果是什么?1,2 还是 2,1,其实都有可能,看实际node运行的结果:

5.png

可以看到两次执行的结果不一样,为什么呢?原因在于即使setTimeout的第二个参数默认为0,但实际上,Node做不到0秒就执行其回调,最少也要4毫秒。那么进入事件循环后,如果没到4毫秒,那么timers阶段就会被跳过,从而进入check阶段执行setImmediate回调,此时输出结果是:2,1;

如果进入事件循环后,超过4毫秒(只是个大概,具体值并不确定),setTimeout的回调会出现在timer阶段的队列里,回调将被执行,之后再进入poll阶段和check阶段,此时输出结果是:1,2

那如果两者在I/O周期内调用,谁先执行呢?看一下代码:

const fs = require('fs')fs.readFile('./test.txt', 'utf8' , (err, data) => {  if (err) {    console.error(err)    return  }  setTimeout(() => {    console.log('timeout');  }, 0);  setImmediate(() => {    console.log('immediate');  });})

登录后复制

实际上,node中输出的结果总是immediate先输出,timeout后输出。因为I/O回调是在poll阶段执行,当回调执行完毕之后队列为空,发现存在setImmediate的回调就会进入check阶段,执行完毕后,再进入timer阶段。

四、总结

本文结合代码示例,对node的事件循环机制做了比较详细描述。通过这篇文章,应该可以了解浏览器的事件循环机制是怎样的,node的循环机制是怎样的,以及nextTick和micro队列的优先级,setTimout和setImmediate执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

更多编程相关知识,请访问:node!!

以上就是详解nodejs中的事件循环机制的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2634426.html

(0)
上一篇 2025年3月7日 04:32:35
下一篇 2025年2月18日 00:47:31

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 详解vscode中如何更简单、有效地调试Node.js程序!!

    本篇文章给大家介绍一下使用vscode调试node.js的超简单方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 推荐学习:《vscode》、《vscode》 让我们面对现实吧…调试 Node.js 一直是…

    2025年3月7日 编程技术
    200
  • 深入浅析Nodejs中的“洋葱模型”

    本篇文章带大家了解一下nodejs中的“洋葱模型”。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 目前比较流行的 Node.js 框架有Express、KOA和Egg.js,其次是另外一个正在兴起的与 TypeScrip…

    2025年3月7日 编程技术
    200
  • 浅谈Nodejs中的callback回调

    本篇文章给大家介绍一下nodejs中的callback回调。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 推荐学习:《nodejs》 什么是callback?  很显然,字面意思就是回调 那为什么在Node中需要回调操作…

    2025年3月7日
    200
  • Nodejs如何实现简单的GET请求

    本篇文章给大家介绍一下node.js实现简单的get请求的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 【推荐学习:《nodejs 教程》】 GET请求的识别非常的简单,就是URL的识别,使用的是url.parse…

    2025年3月7日 编程技术
    200
  • 聊聊Nodejs中的模块化和事件循环

    本篇文章带大家了解一下nodejs中的模块化和事件循环。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 5.20出了一款线上Ide,能够在浏览器上边运行 Node.js —nodejs 1 Node.js简介 Node.j…

    2025年3月7日 编程技术
    200
  • 深入浅析Node.js中的异步

    本篇文章给大家详细介绍一下node.js中的异步。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 关于 Node.js 异步,绕不开两点:非阻塞 I/O 和事件循环。也正是因为这两点,Node.js 才能被称为高性能并运用…

    2025年3月7日
    200
  • 浅谈Nodejs中的模块规范

    本篇文章给大家详细了解一下nodejs中的模块规范。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 模块规范是构建一个大型 Node.js 应用的基础,所以非常重要;Node.js 模块规范也就是 CommonJS 模块规…

    2025年3月7日 编程技术
    200
  • 浅谈使用Node.js搭建一个简单的 HTTP 服务器

    本篇文章给大家使用node.js搭建一个简单的 http 服务器来试着操纵计算机资源。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 什么是 HTTP 服务? HTTP 协议是什么? 超文本传输协议,一个应用层协议,一个在…

    2025年3月7日 编程技术
    200
  • 深入了解Node.js中的Koa框架

    本篇文章给大家详细介绍一下node.js中的koa框架。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 在前文已经简单的了解了 Express 框架,并且使用该框架对石头剪刀布游戏进行了改造,那么来看看 Koa 框架和 E…

    2025年3月7日
    200
  • 浅谈Nodejs中的可读流,可读流如何实现?

    本篇文章给大家介绍一下nodejs中的流(stream),看看node可读流的实现方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 stream的概念 流(stream)是 Node.js 中处理流式数据的抽象接口。 …

    2025年3月7日 编程技术
    200

发表回复

登录后才能评论