彻底搞懂JavaScript事件循环(下)

简介: 1. 异步执行原理(1)单线程的JavaScript我们知道,JavaScript是一种单线程语言,它主要用来与用户互动,以及操作DOM。JavaScript 有同步和异步的概念,这就解决了代码阻塞的问题:同步:如果在一个函数返回的时候,调用者就能够得到预期结果,那么这个函数就是同步的;异步:如果在函数返回的时候,调用者还不能够得到预期结果

(2)事件循环的流程

其中libuv引擎中的事件循环分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。下面 是Eventloop 事件循环的流程:


网络异常,图片无法展示
|


整个流程分为六个阶段,当这六个阶段执行完一次之后,才可以算得上执行了一次 Eventloop 的循环过程。下面来看下这六个阶段都做了哪些事:

  1. timers 阶段:执行timer(setTimeout、setInterval)的回调,由 poll 阶段控制;
  2. I/O callbacks 阶段:主要执行系统级别的回调函数,比如 TCP 连接失败的回调;
  3. idle, prepare 阶段:仅Node.js内部使用,可以忽略;
  4. poll 阶段:轮询等待新的链接和请求等事件,执行 I/O 回调等;
  5. check 阶段:执行 setImmediate() 的回调;
  6. close callbacks 阶段:执行关闭请求的回调函数,比如socket.on('close', ...)


注意:上面每个阶段都会去执行完当前阶段的任务队列,然后继续执行当前阶段的微任务队列,只有当前阶段所有微任务都执行完了,才会进入下个阶段,这里也是与浏览器中逻辑差异较大的地方。


其中,这里面比较重要的就是第四阶段:poll,这一阶段中,系统主要做两件事:

  • 回到 timer 阶段执行回调
  • 执行 I/O 回调

在进入该阶段时如果没有设定了 timer 的话,会出现以下情况:


(1)如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制;

(2)如果 poll 队列为空时,会出现以下情况:

  • 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调;
  • 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去;

当设定了 timer 且 poll 队列为空,则会判断是否有 timer 超时,如果有的就会回到 timer 阶段执行回调。


这一过程的具体执行流程如下图所示:

网络异常,图片无法展示
|


(3)宏任务和微任务

Node.js事件循环的异步队列也分为两种:宏任务队列和微任务队列。

  • 常见的宏任务:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等。
  • 常见的微任务:process.nextTick、new Promise().then(回调)等。


(4)process.nextTick()

上面提到了process.nextTick(),它是node中新引入的一个任务队列,它会在上述各个阶段结束时,在进入下一个阶段之前立即执行。


Node.js官方文档的解释如下:

process.nextTick()is not technically part of the event loop. Instead, thenextTickQueuewill be processed after the current operation is completed, regardless of the current phase of the event loop. Here, an operation is defined as a transition from the underlying C/C++ handler, and handling the JavaScript that needs to be executed.

例如下面的代码:

setTimeout(() => {
    console.log('timeout');
}, 0);
Promise.resolve().then(() => {
    console.error('promise')
})
process.nextTick(() => {
    console.error('nextTick')
})
复制代码


输出结果如下:

nextTick
promise
timeout
复制代码


可以看到,process.nextTick()是优先于promise的回调执行。


(5)setImmediate 和 setTimeout

上面还提到了setImmediate 和 setTimeout,这两者很相似,主要区别在于调用时机的不同:

  • setImmediate:在poll阶段完成时执行,即check阶段;
  • setTimeout:在poll阶段为空闲时,且设定时间到达后执行,但它在timer阶段执行;

例如下面的代码:

setTimeout(() => {
  console.log('timeout');
}, 0);
setImmediate(() => {
  console.log('setImmediate');
});
复制代码


输出结果如下:

timeout
setImmediate
复制代码


在上面代码的执行过程中,第一轮循环后,分别将 setTimeout  和 setImmediate 加入了各自阶段的任务队列。第二轮循环首先进入timers 阶段,执行定时器队列回调,然后 pending callbackspoll 阶段没有任务,因此进入check 阶段执行 setImmediate 回调。所以最后输出为timeout、setImmediate。


4. Node与浏览器Event Loop差异


Node.js与浏览器的 Event Loop 差异如下:

  • Node.js:microtask 在事件循环的各个阶段之间执行;
  • 浏览器:microtask 在事件循环的 macrotask 执行完之后执行;

网络异常,图片无法展示
|
Nodejs和浏览器的事件循环流程对比如下:


  1. 执行全局的 Script 代码(与浏览器无差);
  2. 把微任务队列清空:注意,Node 清空微任务队列的手法比较特别。在浏览器中,我们只有一个微任务队列需要接受处理;但在 Node 中,有两类微任务队列:next-tick 队列和其它队列。其中这个 next-tick 队列,专门用来收敛 process.nextTick 派发的异步任务。在清空队列时,优先清空 next-tick 队列中的任务,随后才会清空其它微任务
  3. 开始执行 macro-task(宏任务)。注意,Node 执行宏任务的方式与浏览器不同:在浏览器中,我们每次出队并执行一个宏任务;而在 Node 中,我们每次会尝试清空当前阶段对应宏任务队列里的所有任务(除非达到系统限制);
  4. 步骤3开始,会进入 3 -> 2 -> 3 -> 2…的循环。


相关文章
|
3月前
|
设计模式 JavaScript API
Node.js 事件循环
Node.js 事件循环
26 2
|
4月前
|
前端开发 JavaScript UED
深入理解JavaScript中的事件循环机制
JavaScript中的事件循环机制是其异步编程的核心,深入理解该机制对于开发高效、流畅的前端应用至关重要。本文将介绍事件循环的工作原理、常见的事件循环模型,以及如何利用这些知识解决前端开发中的常见问题。
|
4天前
|
JavaScript API 数据库
深入理解Node.js事件循环及其在后端开发中的应用
【9月更文挑战第3天】本文将深入浅出地介绍Node.js的事件循环机制,探讨其非阻塞I/O模型和如何在后端开发中利用这一特性来处理高并发请求。通过实际的代码示例,我们将看到如何有效地使用异步操作来优化应用性能。文章旨在为读者揭示Node.js在后端开发中的核心优势和应用场景,帮助开发者更好地理解和运用事件循环来构建高性能的后端服务。
|
13天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的图像识别技术深入理解Node.js事件循环及其在后端开发中的应用
【8月更文挑战第27天】本文将介绍深度学习中的图像识别技术,包括其原理、应用领域及未来发展。我们将探讨如何通过神经网络实现图像识别,并分析其在医疗、交通等领域的应用。最后,我们将展望图像识别技术的发展前景。
|
12天前
|
运维 Cloud Native JavaScript
云端新纪元:云原生技术深度解析深入理解Node.js事件循环及其在异步编程中的应用
【8月更文挑战第27天】随着云计算技术的飞速发展,云原生已成为推动现代软件开发和运维的关键力量。本文将深入探讨云原生的基本概念、核心价值及其在实际业务中的应用,帮助读者理解云原生如何重塑IT架构,提升企业的创新能力和市场竞争力。通过具体案例分析,我们将揭示云原生技术背后的哲学思想,以及它如何影响企业决策和操作模式。
|
12天前
|
运维 JavaScript 安全
自动化运维:使用Ansible简化日常任务深入理解Node.js事件循环和异步编程
【8月更文挑战第27天】在快节奏的技术环境中,自动化不再是奢侈品,而是必需品。本文将引导你通过Ansible实现自动化运维,从基础到高级应用,解锁高效管理服务器群的秘诀,让你的IT操作更加流畅和高效。
|
4月前
|
Web App开发 JavaScript 前端开发
浏览器与Node.js事件循环:异同点及工作原理
浏览器与Node.js事件循环:异同点及工作原理
|
8天前
|
JavaScript 前端开发 开发者
深入理解Node.js中的事件循环
【8月更文挑战第31天】在Node.js的世界里,“事件循环”是心脏,它驱动着异步操作的脉搏。本文将带你走进事件循环的核心,通过简单的例子揭示其神秘面纱。你将看到如何利用事件循环来处理并发任务,以及它是如何在背后默默支撑起你的后端应用。准备好,让我们一起探索这个让Node.js与众不同的特性吧!
|
8天前
|
JavaScript 开发者
深入理解Node.js事件循环及其在后端开发中的应用
【8月更文挑战第31天】 本文将带你走进Node.js的事件循环机制,通过浅显易懂的语言和实例代码,揭示其背后的工作原理。我们将一起探索如何高效利用事件循环进行异步编程,提升后端应用的性能和响应速度。无论你是Node.js新手还是有一定经验的开发者,这篇文章都能给你带来新的启发和思考。
|
8天前
|
JavaScript 前端开发 数据库
深入理解Node.js中的事件循环和异步编程
【8月更文挑战第31天】 本文旨在揭示Node.js中事件循环的神秘面纱,通过浅显易懂的语言和生动的比喻,我们将一同走进异步编程的世界。你将了解事件循环如何协调任务执行,掌握异步操作背后的原理,并通过实际代码示例,学会如何在Node.js中高效地处理异步任务。让我们开始探索这段奇妙的旅程!