Node.js 事件循环:定时任务、延迟任务和 I/O 事件的艺术

news2024/11/20 8:44:24

在这里插入图片描述

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6
🍨 阿珊和她的猫_CSDN个人主页
🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》
🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入门到实战全面掌握 uni-app》

文章目录

  • 定时器和延时器
    • 使用 setTimeout 和 setInterval 实现定时任务
    • 解释定时器和延时器在事件循环中的工作原理
  • I/O 事件
    • 文件操作和网络请求等 I/O 事件的处理
    • 理解 I/O 事件在事件循环中的优先级和回调函数的执行时机
  • 拓展话题:Promise 和 Async/Await
    • 介绍 Promise 和 Async/Await 与事件循环的关系
    • 如何使用 Promise 和 Async/Await 优化事件循环的性能
  • 总结与展望
    • 总结 Node.js 事件循环的核心概念和工作原理

定时器和延时器

使用 setTimeout 和 setInterval 实现定时任务

在 Node.js 中,你可以使用 setTimeoutsetInterval 函数来实现定时任务。
这两个函数都接受一个回调函数作为参数,并在指定的时间后执行该回调函数。

以下是使用 setTimeoutsetInterval 实现定时任务的示例代码:

// 使用 setTimeout 实现定时任务
setTimeout(() => {
  console.log('定时任务执行');
}, 1000); // 1000 毫秒后执行回调函数

// 使用 setInterval 实现定时任务
setInterval(() => {
  console.log('每隔 1 秒执行定时任务');
}, 1000); // 每隔 1 秒执行回调函数

在上面的示例中,我们使用 setTimeout 函数设置了一个定时任务,它将在 1 秒后执行。然后,我们使用 setInterval 函数设置了一个每隔 1 秒执行一次的定时任务。

请注意,setTimeoutsetInterval 函数返回一个定时器标识符,你可以使用这个标识符来取消定时任务。例如:

let timerId = setTimeout(() => {
  console.log('定时任务执行');
}, 1000);

clearTimeout(timerId); // 取消定时任务

在上面的示例中,我们使用 clearTimeout 函数取消了之前设置的定时任务。同样,你也可以使用 clearInterval 函数来取消 setInterval 设置的定时任务。

解释定时器和延时器在事件循环中的工作原理

定时器(setTimeoutsetInterval)和延时器(setImmediatesetTimeout函数的第二个参数)在事件循环中以不同的方式工作。

  1. setTimeoutsetInterval:这两个函数将回调函数添加到定时器事件队列(timers)中。当事件循环到达 timers 阶段时,它会按照到期时间的顺序执行回调函数。

    • setTimeout 函数接受两个参数:回调函数和延迟时间(以毫秒为单位)。它将回调函数添加到定时器事件队列中,并在指定的延迟时间后执行。
    • setInterval 函数与 setTimeout 类似,除了它会在每次回调函数执行后重新将其添加到定时器事件队列中,以实现周期性执行。
  2. setImmediate:这个函数将回调函数添加到 setImmediate 队列中。setImmediate 队列中的回调函数会在事件循环的 check 阶段被执行,并且在 timers 队列之前。

    • setTimeout 不同,setImmediate 的回调函数会在当前事件循环迭代中尽快执行,而不是在特定的延迟时间后执行。

    • 由于 setImmediate 队列在 timers 队列之前执行,因此如果同时使用 setTimeoutsetImmediatesetImmediate 队列中的回调函数会先执行。

需要注意的是,setImmediate 并不是在所有环境中都被支持,它是 Node.js 中的一个扩展,而在浏览器环境中不被支持。在浏览器中,可以使用 Promisethen 方法或 MutationObserver 来实现类似的立即执行功能。

I/O 事件

文件操作和网络请求等 I/O 事件的处理

文件操作和网络请求等 I/O 事件的处理在事件循环中通过 I/O 事件队列(I/O)进行处理。

当进行文件操作或发起网络请求时,Node.js 会将相应的 I/O 事件添加到 I/O 事件队列中。

事件循环会在 I/O 阶段检查 I/O 事件队列是否有事件。如果有事件,事件循环会从队列中取出事件并执行相应的回调函数。在文件操作中,例如读取文件或写入文件,回调函数将在文件操作完成后被执行。在网络请求中,例如 HTTP 请求,回调函数将在响应数据可用时被执行。

I/O 事件的处理是非阻塞的,这意味着在执行 I/O 操作时,事件循环不会被阻塞
相反,它会继续处理其他队列中的事件
当 I/O 操作完成并将事件添加到 I/O 事件队列中时,事件循环会在适当的时候处理这些事件。

这种非阻塞 I/O 模型使得 Node.js 能够高效地处理大量的 I/O 操作,因为它不会因为等待 I/O 操作完成而阻塞事件循环。
相反,它可以同时处理多个 I/O 操作,并在它们完成时逐个处理它们的回调函数。

理解 I/O 事件在事件循环中的优先级和回调函数的执行时机

在事件循环中,I/O 事件的优先级与其他事件队列(如定时器事件队列和 setImmediate 队列)的优先级不同。

具体来说,I/O 事件的优先级较低,它们会在其他事件队列中的事件处理完毕后被处理。

回调函数的执行时机取决于事件的类型和处理方式。对于文件操作和网络请求等 I/O 事件,回调函数将在文件操作完成或响应数据可用时被执行。在文件操作中,例如读取文件或写入文件,回调函数将在文件操作完成后被执行。在网络请求中,例如 HTTP 请求,回调函数将在响应数据可用时被执行。

需要注意的是,I/O 事件的处理是非阻塞的,这意味着在执行 I/O 操作时,事件循环不会被阻塞。相反,它会继续处理其他队列中的事件。当 I/O 操作完成并将事件添加到 I/O 事件队列中时,事件循环会在适当的时候处理这些事件。

这种非阻塞 I/O 模型使得 Node.js 能够高效地处理大量的 I/O 操作,因为它不会因为等待 I/O 操作完成而阻塞事件循环。相反,它可以同时处理多个 I/O 操作,并在它们完成时逐个处理它们的回调函数。

拓展话题:Promise 和 Async/Await

介绍 Promise 和 Async/Await 与事件循环的关系

Promise 和 Async/Await 是 JavaScript 中的异步编程解决方案,它们与事件循环密切相关。

Promise 是一种用于处理异步操作结果的对象。它可以表示一个异步操作的最终完成(fulfillment)或失败(rejection)。Promise 对象有一个 then 方法,用于注册在异步操作完成后执行的回调函数。

在事件循环中,Promise 的处理通过 Promise 队列(promise)进行。当创建一个 Promise 对象时,它会被添加到 Promise 队列中。事件循环会在 Promise 阶段检查 Promise 队列是否有等待处理的 Promise 对象。如果有,事件循环会取出队列中的 Promise 对象并执行其回调函数。

Async/Await 是基于 Promise 的语法糖,它使得异步代码的编写更加简洁和直观。使用 Async/Await,你可以将异步操作编写为类似同步代码的形式,通过使用 async 关键字定义异步函数,并使用 await 关键字等待 Promise 的完成。

在 Async/Await 的情况下,异步操作的执行仍然通过 Promise 进行,但异步函数的执行会被暂停,直到等待的 Promise 对象完成。当遇到 await 关键字时,JavaScript 引擎会将异步函数的执行挂起,并将其添加到微任务队列(microtask)中。事件循环会在 microtask 阶段检查微任务队列,并执行其中的微任务。

因此,Promise 和 Async/Await 都是 JavaScript 中处理异步操作的方式,它们通过事件循环来协调异步操作的执行。Promise 通过 Promise 队列进行处理,而 Async/Await 通过将异步函数的执行挂起并添加到微任务队列中来实现异步操作的等待和处理。事件循环会按照特定的顺序处理这些队列中的事件,以实现异步代码的非阻塞执行。

如何使用 Promise 和 Async/Await 优化事件循环的性能

使用 Promise 和 Async/Await 可以在一定程度上优化事件循环的性能。

以下是一些建议:

在这里插入图片描述

  1. 使用 Promise.all:如果你有多个异步操作需要同时完成,可以使用 Promise.all 方法。它接受一个 Promise 对象数组作为参数,并返回一个新的 Promise 对象,该对象在所有传入的 Promise 对象都完成后才会完成。这样可以避免多个异步操作的回调函数在事件循环中被多次处理,提高性能。

  2. 使用 Async/Await: Async/Await 是基于 Promise 的语法糖,它使得异步代码的编写更加简洁和直观。使用 Async/Await 可以避免回调地狱的问题,并使代码更易于阅读和维护。在使用 Async/Await 时,注意将异步操作放在 async 函数中,并使用 await 关键字等待 Promise 的完成。

  3. 避免嵌套的异步操作:过多的嵌套异步操作可能会导致事件循环的性能下降。尽量保持异步操作的层次结构简单,并避免在回调函数中再次进行异步操作。如果需要处理嵌套的异步操作,可以使用 Promise 的链式调用或使用 async/await 来简化代码。

  4. 使用微任务:在 Async/Await 中,遇到 await 关键字时,JavaScript 引擎会将异步函数的执行挂起,并将其添加到微任务队列中。微任务队列会在事件循环的 microtask 阶段被处理。利用微任务队列可以实现一些性能优化,例如在微任务队列中进行一些轻量级的操作,以避免阻塞事件循环。

  5. 合理使用 Promise 的 resolve 和 reject:在创建 Promise 对象时,尽量避免在 Promise 的构造函数中传入立即 resolve 或 reject 的值。这可能会导致不必要的微任务被添加到微任务队列中,从而影响性能。尽量在需要时才 resolve 或 reject Promise 对象。

通过合理使用 Promise 和 Async/Await,可以使事件循环的性能得到一定程度的优化。但具体的优化效果还需要根据实际情况进行评估和测试,以确保在你的应用中获得最佳性能。

总结与展望

总结 Node.js 事件循环的核心概念和工作原理

Node.js 事件循环的核心概念和工作原理可以总结如下:

  1. 事件循环:Node.js 是基于事件驱动的非阻塞 I/O 模型。事件循环是 Node.js 处理事件的核心机制,它负责监听和处理各种事件,并在需要时执行相应的回调函数。

  2. 事件队列:事件循环使用事件队列来管理事件。有不同类型的事件队列,包括定时器事件队列、I/O 事件队列和 setImmediate 队列。这些队列按照特定的顺序进行处理。

  3. 阶段:事件循环分为多个阶段,包括 timers 阶段、I/O 阶段和 poll 阶段等。每个阶段会处理特定类型的事件。

  4. 回调函数:当事件发生时,相应的回调函数会被添加到事件队列中。事件循环会在适当的阶段取出队列中的回调函数并执行它们。

  5. 非阻塞 I/O:Node.js 使用非阻塞 I/O 模型,这意味着在执行 I/O 操作时,事件循环不会被阻塞。相反,它会将 I/O 操作委托给操作系统,并在 I/O 操作完成后触发相应的事件。

  6. 异步编程:由于 Node.js 的非阻塞特性,异步编程成为常见的编程方式。使用异步函数和回调函数可以在不阻塞事件循环的情况下执行异步操作。

  7. 定时器和延时器:定时器和延时器是在事件循环中管理时间相关操作的重要工具。定时器会在指定的时间后将回调函数添加到定时器事件队列中,而延时器会在当前事件循环迭代结束后将回调函数添加到下一个事件循环迭代的 poll 阶段。

通过事件循环,Node.js 能够高效地处理大量的 I/O 操作和并发请求,提供了一种高效的异步编程模型。了解事件循环的工作原理对于编写高效和可靠的 Node.js 应用至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1263644.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 watch+$nextTick 解决Vue引入组件无法使用问题

问题描述: 很多时候我们都需要使用第三方组件库,比如Element-UI,Swiper 等等。 如果我们想要在这些结构中传入自己从服务器请求中获取的数据就会出现无法显示的问题。 比如我们在下面的Swiper例子中,我们需要new Swiper 才能让…

WordPress自动采集伪原创发布工具

在当今数字化时代,随着信息爆炸式增长,网站内容的更新速度飞快。对于拥有WordPress网站的用户而言,如何轻松而又快速地批量采集伪原创内容成为一项具有挑战性的任务。本文将专心分享一些方法和技巧,帮助WordPress用户实现批量采集…

python学习过程中一些问题记录总结

工作机器上安装了 两个环境 使用anaconda3 时配置仓库地址不能 拉取到 cv2 DBUtils 使用python2 时 版本低,拉取不到 解决办法,python2不支持下载最新版本的,需要指定下载一个老的版本即可,下个1.3的就OK了 pip install DBU…

【随笔】OpenFlow概述

SDN之前的网络结构 硬件 操作系统 网络功能(交换机、路由器、防火墙、VPN、NAT、OSPF、BGP、Traffic Engineering) 北向是SDN应用 SDN控制器 底层是SDN数据面(通用硬件) openflow OPEN NETWORKING FOUNDATION 支持开放的SDN平台,一直使用的是openflow协议。 op…

Vue 双向数据绑定

之前通过v-bind来完成的数据绑定&#xff0c;属性值和表达式进行绑定&#xff0c;表达式的值发生变化了属性值也跟着发生变化。 单向数据绑定&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>首页</titl…

rss服务搭建记录

layout: post title: RSS subtitle: vps搭建RSS服务 date: 2023-11-27 author: Sprint#51264 header-img: img/post-bg-universe.jpg catalog: true tags: - 折腾 文章目录 引言RSShub-dockerRSS-radarFreshrssFluent reader获取fever api配置Fluent Reader同步 结语 引言 一个…

ArrayList源码全面解析

一、概述 ArrayList 是 java 集合框架中比较常用的数据结构,继承自 AbstractList&#xff0c;实现了 List 接口。底层采用数组来实现。ArrayList 实现了java.io.Serializable接口&#xff0c;这意味着ArrayList支持序列化&#xff0c;能通过序列化去传输。 1.1、底层数据结构…

从 Rust 程序员的早期使用印象看 Go

Go vs Rust 我在过去的几周开始使用 Go。这是我第一次在一个较大且严肃的项目中使用 Go。之前我对 Go 有过很多了解&#xff0c;并且在研究 Rust 的特性时&#xff0c;曾经使用例子和玩具程序。然而&#xff0c;真正的编程经验是完全不同的。 我认为写写我对它的印象会很有趣。…

Java(九)(多线程,线程安全,实现线程的方法,线程同步,线程池,并发和并行,线程的六种状态)

目录 多线程 线程 实现线程的方法 方法一:继承Thread父类 方法二:实现Runnable接口 方法三:Callable接口和FutureTask类来实现 Thread方法 线程安全 线程同步 同步代码块 同步方法 Lock锁 线程池 线程池对象的创建方式一: 线程池处理Runnable任务 线程池处理Cal…

办公软件定制开发在企业发展中的优势|app小程序搭建

办公软件定制开发在企业发展中的优势|app小程序搭建 如今&#xff0c;办公软件已经成为企业日常工作的必需品。很多企业为了提高工作效率和满足自身业务需要&#xff0c;选择定制开发办公软件。下面将介绍定制开发办公软件在企业发展中的优势。 1定制开发办公软件可以满足企业…

uni-app 微信小程序 pdf预览

<div click"getPDF">查看体检报告</div>getPDF() {uni.downloadFile({url: ${this.$baseURL}/file/download?id${this.pdfFileId},//编写在线的pdf地址success: function(res) {const filePath res.tempFilePath;uni.openDocument({filePath: filePath…

每日一练【移动零】

一、题目描述 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 二、题目解析 可以…

Appium自动化如果出现报错怎么办?这么做确实解决问题

解决通过appium的inspector功能无法启动app的原因 在打开appium-desktop程序&#xff0c;点击inspector功能&#xff0c;填写app的配置信息&#xff0c;启动服务提示如下&#xff1a; 报错信息&#xff1a; An unknown server-side error occurred while processing the com…

数据结构(超详细讲解!!)第二十五节 树与森林

1.树的存储结构 和线性表一样&#xff0c;树可以用顺序和链式两种存储结构。 树的顺序存储结构适合树中结点比较“满”的情况。根据树的非线性结构特点&#xff0c;常用链式存储方式来表示树。树常用的存储方法有&#xff1a;双亲表示法、孩子表示法和孩子兄弟表…

Toast UI Editor上传图片到Flask

Toast UI Editor国内文档几乎搜不到&#xff0c;国外文档也写得不是特别项目&#xff0c;没有太多举例的demo。一开始选择使用这个就是因为UI好看。不过看看源码把思路滤清了。 他会给把图片转成Base64&#xff0c;到时候发表单直接丢过去就行了&#xff0c;blob这个参数能拿到…

05 Nacos实战:集成Nacos实现分布式配置中心实现配置动态刷新

上一节介绍了Nacos注册中心的功能,这节介绍下木谷博客中如何使用Nacos作为分布式配置中心。 在第二节搭建项目并运行中讲到创建mugu_nacos_config这个数据库,其中已经包含了木谷博客所需的全部配置,在nacos中也可以查看到,如下: 引入Nacos作为配置中心很简单,步骤如下:…

vue3中的customRef创建一个自定义的 ref对象

customRef 创建一个自定义的 ref&#xff0c;并对其依赖项跟踪和更新触发进行显式控制 小案例: 自定义 ref 实现 debounce <template><div style"font-size: 14px;"><input v-model"text" placeholder"搜索关键字"/><…

flask 上传文件

from flask import Flask, request, render_template,redirect, url_for from werkzeug.utils import secure_filename import os from flask import send_from_directory # send_from_directory可以从目录加载文件app Flask(__name__)#UPLOAD_FOLDER media # 注意&#xff…

【TiDB】TiDB离线方式部署

目录 1 下载TiDB离线组件包 2 安装TiUP 3 合并离线包 4 TIDB 软件和硬件环境建议配置 5 TiDB环境与系统配置检查 6 生成集群初始化配置文件模板 7 执行部署命令 1 检查就能存在的潜在风险 2 手动修复风险 3 部署 TiDB 集群 8 查看TIUP管理的集群情况 9 检查部署的…

[⑥ADRV902x]: 软件系统初始化流程学习

前言 本篇博客主要记录ADRV902x参考软件中对ADRV902x系统的初始化流程&#xff0c;使用API函数来实现transceiver的配置&#xff0c;校准和控制等。官方将整个系统初始化称之为multichip synchronization initialization (MCS) sequence&#xff0c;主要分成PreMcsInit&#x…