热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

详解nodejs中的事件循环机制

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

(转引自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。

浏览器里执行结果如下:

六个阶段分别是:

  • 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输出结果确实是这样:

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

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运行的结果:

可以看到两次执行的结果不一样,为什么呢?原因在于即使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执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

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

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


推荐阅读
  • 本文详细解析了如何使用Python的urllib模块发起POST请求,并通过实例展示如何爬取百度翻译的翻译结果。 ... [详细]
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • 本文探讨了2019年前端技术的发展趋势,包括工具化、配置化和泛前端化等方面,并提供了详细的学习路线和职业规划建议。 ... [详细]
  • 在Web开发过程中,Ajax技术常用于前后端数据交互。其中一个重要的属性async决定了请求是否以异步方式执行,本文将详细解析async属性的作用及使用方法。 ... [详细]
  • 理解文档对象模型(DOM)
    本文介绍了文档对象模型(DOM)的基本概念,包括其作为HTML文档的节点树结构,以及如何通过JavaScript操作DOM来实现网页的动态交互。 ... [详细]
  • 本文深入探讨了JavaScript中实现继承的四种常见方法,包括原型链继承、构造函数继承、组合继承和寄生组合继承。对于正在学习或从事Web前端开发的技术人员来说,理解这些继承模式对于提高代码质量和维护性至关重要。 ... [详细]
  • 本文将详细介绍如何在ThinkPHP6框架中实现多数据库的部署,包括读写分离的策略,以及如何通过负载均衡和MySQL同步技术优化数据库性能。 ... [详细]
  • 本文探讨了如何通过WebBrowser控件在用户点击输入框时自动显示图片验证码。该过程可能涉及JavaScript事件的触发与响应。 ... [详细]
  • 本文探讨了浏览器的同源策略限制及其对 AJAX 请求的影响,并详细介绍了如何在 Spring Boot 应用中优雅地处理跨域请求,特别是当请求包含自定义 Headers 时的解决方案。 ... [详细]
  • 深入解析ES6至ES8的新特性与应用
    本文详细介绍了自2015年发布的ECMAScript 6.0(简称ES6)以来,JavaScript语言的多项重要更新,旨在帮助开发者更好地理解和利用这些新特性进行复杂应用的开发。 ... [详细]
  • 本文将介绍如何利用Python爬虫技术抓取国内主流在线学习平台的数据,并以51CTO学院为例,进行详细的技术解析和实践操作。 ... [详细]
  • 深入分析十大PHP开发框架
    随着PHP技术的发展,各类开发框架层出不穷,成为了开发者们热议的话题。本文将详细介绍并对比十款主流的PHP开发框架,旨在帮助开发者根据自身需求选择最合适的工具。 ... [详细]
  • Microsoft即将发布WPF/E的CTP(Community Technology Preview)和SDK,标志着RIA(Rich Internet Application)技术的新里程碑。更多详情及下载链接请参见MSDN官方页面。 ... [详细]
  • ServletContext接口在Java Web开发中扮演着重要角色,它提供了一种方式来获取关于整个Web应用程序的信息。通过ServletContext,开发者可以访问初始化参数、共享数据以及应用资源。 ... [详细]
  • 本文深入探讨网页游戏的开发流程,涵盖从程序框架设计到具体实现的技术细节,旨在为开发者提供全面的指导。 ... [详细]
author-avatar
真的长茎使人愁
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有