setImmediate() vs setTimeout() 在 JavaScript 中的区别

news/2024/9/18 3:33:36/ 标签: javascript, 开发语言, node.js

setImmediate() vs setTimeout() 在 JavaScript 中的区别

在 JavaScript 中,setImmediate()setTimeout() 都用于调度任务,但它们的工作方式不同。

JavaScript 的异步特性

JavaScript 以其非阻塞、异步行为而闻名,尤其是在 Node.js 环境中。如果你曾经参与过涉及定时器或回调的项目,你可能遇到过 setTimeout(),甚至 setImmediate()。乍一看,这两个函数似乎做的是同一件事——调度任务以便稍后运行。但如果你曾经一起运行它们,你可能会注意到一些有趣的行为。

尽管它们的目的相似,但 setImmediate()setTimeout() 在底层的操作方式不同。如果你想知道为什么 setImmediate() 回调似乎一个接一个地运行,而 setTimeout() 回调则是间隔开的,本指南将为你解析其中的原因。

这不仅仅是 JavaScript 的一个怪癖;它与 Node.js 如何管理异步任务密切相关。理解这两个函数之间的差异将帮助你更好地控制代码的时间和执行顺序,这对于大型应用程序尤其重要,因为即使是时间上的微小失误也可能导致难以发现的错误。

我们将深入探讨事件循环,它如何处理这些定时器,以及为什么在一起使用它们时事情并不总是按预期发生。到最后,你将更清楚地了解何时使用 setTimeout()setImmediate(),以满足你所需的时间行为。

行为差异

javascript">setImmediate(() => {console.log("setImmediate 1");
});setTimeout(() => {console.log("setTimeout 1");
}, 0);setTimeout(() => {console.log("setTimeout 2");
}, 0);setImmediate(() => {console.log("setImmediate 2");
});

当你运行这段代码时,你可能期望 setTimeout 回调按定义的顺序执行,然后是 setImmediate 回调。但你在控制台中看到的是:

javascript">setTimeout 1
setImmediate 1
setImmediate 2
setTimeout 2

如果这让你感到困惑,不要担心。让我们解开其中的原因。

事件循环

要理解这一点,我们需要快速了解 Node.js 如何管理异步操作。Node.js 的异步特性核心是事件循环。

在 Node.js 中,事件循环处理不同的阶段,每个阶段负责执行某些类型的回调。它帮助管理非阻塞任务,确保函数可以异步执行。在这些阶段中,有不同的队列。对于本次讨论,有两个队列是重要的:

  • 宏任务队列:这是 setTimeoutsetImmediate 等任务所在的地方。
  • 微任务队列:这是 promises (Promise.then()) 和 process.nextTick() 回调所在的地方。

事件循环的工作原理

要理解 setTimeout()setImmediate() 的工作原理,我们需要看看 Node.js 中的事件循环。事件循环允许 Node.js 处理异步代码。它在不同的阶段处理不同类型的操作,每个阶段负责特定的任务。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │└───────────────────────────┘
  1. 定时器阶段:这是处理 setTimeout() 回调的地方。即使是 0 毫秒的延迟,它们也要等到下一次循环迭代才能执行。
  2. 待处理回调阶段:处理已完成的 I/O 事件,但我们的示例中没有,所以跳过这个阶段。
  3. 检查阶段setImmediate() 回调在这里运行。它们在 I/O 任务之后立即执行,但在 setTimeout() 回调之前。
  4. 轮询阶段:处理新的传入 I/O 操作,如文件读取或网络请求。如果没有 I/O,事件循环会跳过这个阶段。
  5. 下一次循环迭代:在检查阶段之后,事件循环回到处理下一个定时器阶段,在那里 setTimeout() 回调最终运行。

setTimeout() 的 0 延迟

当你使用 setTimeout() 并设置延迟为 0 时,你实际上是在告诉 Node.js 在当前操作完成后尽快运行回调。然而,重要的是要记住,“尽快”仍然取决于事件循环的阶段。

javascript">setTimeout(() => {console.log("setTimeout 1 with 0 delay");
}, 0);setImmediate(() => {console.log("setImmediate 1");
});setTimeout(() => {console.log("setTimeout 2 with 0 delay");
}, 0);

输出结果:

setTimeout 1 with 0 delay
setImmediate 1
setTimeout 2 with 0 delay

即使延迟为 0,setTimeout() 回调仍然需要等待定时器阶段的下一次循环,因此不会立即运行。相反,它被放置在宏任务队列中,以便在下一个可用机会执行。

setImmediate()

另一方面,setImmediate() 设计用于在 I/O 事件完成后执行回调,在同一事件循环迭代中。这意味着 setImmediate() 回调在额外的定时器(如 setTimeout())执行之前被处理,特别是在没有 I/O 的情况下。

在我们的示例中,由于没有 I/O 发生,两个 setImmediate() 回调会一个接一个地执行,然后才轮到第二个 setTimeout() 回调。

为什么 setImmediate 回调会一起运行?

  1. 相同的事件循环周期:两个 setImmediate 调用在事件循环的同一个周期(或循环)中被放置到宏任务队列中。Node.js 按顺序处理这些任务。
  2. 优先于 setTimeout():即使 setTimeout() 设定了 0 延迟,这也不保证立即执行。setImmediate() 回调在当前周期中优先于 setTimeout() 任务。

现实世界的类比

想象一下在餐馆点餐和饮料。

  1. 你点了一道菜(代表 setTimeout(0))。
  2. 厨师将其添加到订单队列中,一旦准备好就会送达。
  3. 同时,你要了一杯水(setImmediate()),由于它快速且容易准备,服务员会在你的食物完成之前立即送达。

在这个类比中,水(快速任务)首先被处理,即使两个订单几乎同时下达。菜(稍微复杂一些)稍后送达。

这种情况总是发生吗?

不一定。setImmediate()setTimeout() 的行为可能取决于代码中发生的其他异步操作。如果有 I/O 操作,执行顺序可能会改变,因为 setImmediate() 只会在 I/O 事件完成后运行。

javascript">const fs = require("fs");fs.readFile("example.txt", () => {setTimeout(() => {console.log("setTimeout after I/O");}, 0);setImmediate(() => {console.log("setImmediate after I/O");});
});

输出结果:

setImmediate after I/O
setTimeout after I/O

在这种情况下,setImmediate() 总是在 setTimeout() 之前运行,因为事件循环在 I/O 回调之后优先处理 setImmediate()

当没有 I/O 事件时,两个 setImmediate() 回调会一个接一个地运行,然后才轮到 setTimeout() 回调。

process.nextTick() 和 Promises

以下示例展示了 Node.js 中各种异步操作的处理方式:

javascript">setTimeout(() => {console.log("setTimeout");
}, 0);setImmediate(() => {console.log("setImmediate");
});Promise.resolve().then(() => {console.log("Promise then");
});process.nextTick(() => {console.log("process.nextTick");
});

输出结果:

process.nextTick
Promise then
setTimeout
setImmediate
  • process.nextTick():这将在任何其他任务之前运行,甚至在微任务(如 Promises)之前。
  • Promise.then():这是一个微任务,因此它在当前操作之后但在宏任务(如 setTimeout()setImmediate())之前运行。
  • setTimeout():在微任务处理完之后运行。
  • setImmediate():尽管它类似于 setTimeout(),但它在事件循环周期的后期运行,在当前 I/O 操作之后。

Node.js 的异步行为有时可能会令人困惑,特别是在处理 setTimeout()setImmediate() 时。关键是理解事件循环以及任务在不同阶段的调度方式。

  • setImmediate() 在 I/O 事件之后和当前事件循环周期内运行。
  • setTimeout() 在指定的延迟之后运行,即使延迟为 0,它也会为下一次事件循环迭代调度任务。
  • 当没有 I/O 操作时,setImmediate() 会在下一个 setTimeout() 之前连续执行。

理解这些差异有助于你精确控制代码的运行时间,这在高性能应用程序中至关重要,因为时间和效率非常重要。


http://www.ppmy.cn/news/1525933.html

相关文章

[000-002-01].第03节:Linux系统下Oracle的安装与使用

2.1.Docker安装Oracle 在CentOS7中使用Docker安装Oracle&#xff1a; 1.安装Docker,详细请参考&#xff1a;https://blog.csdn.net/weixin_43783284/article/details/1211403682.拉取镜像&#xff1a; docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g3.下载…

用Blender来烘培模型材质

通常我们在做三维设计&#xff0c;游戏开发的时候&#xff0c;经常需要从网上下载一些3D模型&#xff0c;这些模型采用的材质分辨率通常都不一样&#xff0c;而我们从性能考虑&#xff0c;需要对材质进行统一的处理&#xff0c;例如把材质都统一为2K的分辨率。 我们可以在Blen…

105页PPT:华为ISC集成供应链变革、模式与方法解析

105页PPT:华为ISC变革、模式与方法PPT,下载链接见文末~ 华为ISC&#xff08;Integrated Supply Chain&#xff0c;集成供应链&#xff09;规划管理与变革是华为公司战略转型和提升核心竞争力的关键举措之一。通过多年的努力与实践&#xff0c;华为已经成功构建了一个高效、协同…

怎么把网站设置成HTTPS访问?

有很多的网站尤其是公司网站都是可以HTTPS访问的&#xff0c;而且在地址栏前面还会显示安全锁&#xff0c;这就是HTTPS证书所起到的作用。没有安装HTTPS证书的网站强制用HTTPS访问会被浏览器提醒不安全。那么我们怎么解决这个问题呢&#xff1f; 解决办法 一&#xff1a;首先…

Unity的Button组件进行扩展

废话不多说,在Untiy中,如果想要对Button等组件进行扩展的话,那么不仅仅只需要将新增的属性设置为public或者增加SerializeField字段就行了的,同时需要对Inspector的GUI面板进行修改,以下直接展示代码: using UnityEngine; using UnityEngine.EventSystems; using UnityE…

17、电科院FTU检测标准学习笔记-录波性能

作者简介&#xff1a; 本人从事电力系统多年&#xff0c;岗位包含研发&#xff0c;测试&#xff0c;工程等&#xff0c;具有丰富的经验 在配电自动化验收测试以及电科院测试中&#xff0c;本人全程参与&#xff0c;积累了不少现场的经验 ———————————————————…

Qt 菜单、工具栏 的基本使用

效果 代码 #include "mainwindow.h" #include "ui_mainwindow.h" #include<QToolBar> #include<QDebug> #include<QPushButton>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupU…

【AcWing】快速排序的Go实现

快速排序的Go实现 这一部分参考了AcWing当中使用Go语言实现快速排序的题解&#xff1a;https://www.acwing.com/activity/content/code/content/296206/。 其中有很多部分非常值得参考&#xff0c;故写一个博客进行记录。 Code package mainimport "fmt"func qui…

后端开发刷题 | 矩阵的最小路径和

描述 给定一个 n * m 的矩阵 a&#xff0c;从左上角开始每次只能向右或者向下走&#xff0c;最后到达右下角的位置&#xff0c;路径上所有的数字累加起来就是路径和&#xff0c;输出所有的路径中最小的路径和。 数据范围: 1≤n,m≤500&#xff0c;矩阵中任意值都满足 0≤ai,j…

基于VUE的在线音乐播放管理系统

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于VUE的在线音乐播放管理系统3是前后端分离项目&#xff0c;拥有两种角色 管理员&#xff1a;用户管理、收藏管理、歌手管理、歌曲管理、歌单管理、评论管理等 用户&#xff1a;登录注…

DFS 算法:洛谷B3625迷宫寻路

我的个人主页 {\large \mathsf{{\color{Red} 我的个人主页} } } 我的个人主页 往 {\color{Red} {\Huge 往} } 往 期 {\color{Green} {\Huge 期} } 期 文 {\color{Blue} {\Huge 文} } 文 章 {\color{Orange} {\Huge 章}} 章 DFS 算法&#xff1a;记忆化搜索DFS 算法&#xf…

rust解说

Rust 是一种开源的系统编程语言&#xff0c;由 Mozilla 研究院开发&#xff0c;旨在提供高性能、内存安全且并发性良好的编程体验。 Rust 于 2010 年由 Graydon Hoare 开始设计&#xff0c;并在 2015 年发布了第一个稳定版本。 Rust 的设计目标是解决 C 等传统系统编程语言在…

Java LinkedList 总结

1.特点 LinkedList的内部实现类似于链表&#xff0c;所以使用方法也和链表类似。 2.常用方法 LinkedList list new LinkedList();//创建//插入数据 list.add("jojo");//在尾部插入 list.add(1,"jojo");//在指定位置插入list.addFirst("jojo"…

Docker:对已有的容器,对当前容器映射的端口实时 (增删改查)

首先我的docker已经起了一个容器&#xff0c;我突然想把他的80->80映射的端口改成80->8080 但是我不想去新启动容器&#xff0c;想在现有容器基础上去修改&#xff0c;或者我想删除某个端口映射&#xff08;只是大概思路&#xff09; 如何寻找容器配置文件位置 首先我这…

S3C2440 ARM设备驱动(boot loader,kernel,rootfs)

一、开发板Linux启动需求 1、bootloader 为内核启动准备环境&#xff0c;并引导内核启动 2、kernel&#xff08;linux内核&#xff09; 操作系统的核心&#xff0c;&#xff08;狭义上的操作系统&#xff09; 3、rootfs 一堆有组织的文件 1. bootloader(一个裸机程序) 初始化C…

LINUX网络编程:http

目录 1.认识http请求的字段 2.HTTP请求类 3.认识HTTP应答字段 4.HTTP应答类 5.源代码 协议就是一种约定&#xff0c;http也并不例外&#xff0c;使用http也无非就是&#xff0c;定义一个http请求的结构体&#xff0c;将结构体序列化为字符串&#xff0c;发送给服务器&…

计算机网络408考研 2022

https://zhuanlan.zhihu.com/p/695446866 1 1 1SDN代表软件定义网络。它是一种网络架构&#xff0c;旨在通过将网络控制平面从数据转发平面分离出来&#xff0c;从而实现网络的灵活性和可编程性。在SDN中&#xff0c;网络管理员可以通过集中式控制器 来动态管理网络流量&…

Python基础语法(1)上

常量和表达式 我们可以把 Python 当成一个计算器&#xff0c;来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问&#xff0c;为什么不是1.6666666666666667呢&#xff1f; 其实在编程中&#xff0c;一般没有“四舍五入”这样的规则…

Haskell中的数据交换:通过http-conduit发送JSON请求

无论是客户端与服务器之间的通信&#xff0c;还是服务之间的API调用&#xff0c;都需要一种高效、可靠的方法来传输数据。在众多编程语言中&#xff0c;Haskell以其强大的类型系统和函数式编程特性&#xff0c;为构建可靠和高效的数据交换提供了坚实的基础。本文将介绍如何在Ha…

支付域——聚合支付设计

摘要 聚合支付是支付行业的一项重要创新&#xff0c;通过整合多种支付方式&#xff0c;极大简化了支付流程&#xff0c;提升了交易效率&#xff0c;为商户和消费者提供了更加便捷的支付体验。随着移动支付的普及&#xff0c;聚合支付在未来的支付场景中将继续发挥重要作用&…