彻底搞懂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…的循环。


相关文章
|
2月前
|
存储 JavaScript 前端开发
深入理解JavaScript中的事件循环(Event Loop):机制与实现
【10月更文挑战第12天】深入理解JavaScript中的事件循环(Event Loop):机制与实现
84 3
|
3月前
|
JavaScript 前端开发 API
详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务
该文章详细讲解了队列数据结构在前端开发中的应用,并深入探讨了JavaScript的事件循环机制,区分了宏任务和微任务的执行顺序及其对前端性能的影响。
|
28天前
|
JavaScript 前端开发 开发者
JavaScript的事件循环
【10月更文挑战第27天】理解JavaScript的事件循环机制对于正确编写和理解JavaScript中的异步代码至关重要,它是JavaScript能够高效处理各种异步任务的关键所在。
33 1
|
2月前
|
前端开发 JavaScript
深入理解JavaScript中的事件循环(Event Loop):从原理到实践
【10月更文挑战第12天】 深入理解JavaScript中的事件循环(Event Loop):从原理到实践
37 1
|
2月前
|
Web App开发 JavaScript 前端开发
深入理解Node.js事件循环和异步编程模型
【10月更文挑战第9天】在JavaScript和Node.js中,事件循环和异步编程是实现高性能并发处理的基石。本文通过浅显易懂的语言和实际代码示例,带你一探究竟,了解事件循环的工作原理及其对Node.js异步编程的影响。从基础概念到实际应用,我们将一步步解锁Node.js背后的魔法,让你的后端开发技能更上一层楼!
|
2月前
|
设计模式 JavaScript API
Node.js 事件循环
10月更文挑战第3天
30 0
Node.js 事件循环
|
2月前
|
JavaScript 调度 数据库
深入浅出:Node.js中的异步编程与事件循环
【9月更文挑战第30天】在Node.js的世界里,理解异步编程和事件循环是掌握其核心的关键。本文将通过浅显易懂的语言和实际代码示例,带你探索Node.js如何处理并发请求,以及它是如何在幕后巧妙地调度任务的。我们将一起了解事件循环的各个阶段,并学会如何编写高效的异步代码,让你的应用程序运行得更加流畅。
64 10
|
2月前
|
JavaScript 前端开发 开发者
深入理解Node.js中的事件循环和异步编程
【9月更文挑战第31天】本文旨在揭示Node.js背后的强大动力——事件循环机制,并探讨其如何支撑起整个异步编程模型。我们将深入浅出地分析事件循环的工作原理,以及它如何影响应用程序的性能和稳定性。通过直观的例子,我们会展示如何在实际应用中利用事件循环来构建高性能、响应迅速的应用。此外,我们还会讨论如何避免常见的陷阱,确保代码既优雅又高效。无论你是Node.js的新手还是经验丰富的开发者,本篇文章都将为你提供宝贵的洞察和实用技巧。
64 6
|
3月前
|
JavaScript 开发者
深入理解Node.js事件循环及其在后端开发中的应用
【8月更文挑战第57天】本文将带你走进Node.js的事件循环机制,通过浅显易懂的语言和实例代码,揭示其背后的工作原理。我们将一起探索如何高效利用事件循环进行异步编程,提升后端应用的性能和响应速度。无论你是Node.js新手还是有一定经验的开发者,这篇文章都能给你带来新的启发和思考。
|
3月前
|
JavaScript API 数据库
深入理解Node.js事件循环及其在后端开发中的应用
【9月更文挑战第3天】本文将深入浅出地介绍Node.js的事件循环机制,探讨其非阻塞I/O模型和如何在后端开发中利用这一特性来处理高并发请求。通过实际的代码示例,我们将看到如何有效地使用异步操作来优化应用性能。文章旨在为读者揭示Node.js在后端开发中的核心优势和应用场景,帮助开发者更好地理解和运用事件循环来构建高性能的后端服务。