前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >setImmediate() vs setTimeout() 在 JavaScript 中的区别

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

作者头像
ACK
发布于 2024-09-17 23:51:08
发布于 2024-09-17 23:51:08
36900
代码可运行
举报
运行总次数:0
代码可运行

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

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

JavaScript 的异步特性

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

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

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

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

行为差异

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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 处理异步代码。它在不同的阶段处理不同类型的操作,每个阶段负责特定的任务。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   ┌───────────────────────────┐
┌─>│           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
代码运行次数:0
运行
AI代码解释
复制
setTimeout(() => {
  console.log("setTimeout 1 with 0 delay");
}, 0);

setImmediate(() => {
  console.log("setImmediate 1");
});

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

输出结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
const fs = require("fs");

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

  setImmediate(() => {
    console.log("setImmediate after I/O");
  });
});

输出结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
setImmediate after I/O
setTimeout after I/O

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

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

process.nextTick() 和 Promises

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
setTimeout(() => {
  console.log("setTimeout");
}, 0);

setImmediate(() => {
  console.log("setImmediate");
});

Promise.resolve().then(() => {
  console.log("Promise then");
});

process.nextTick(() => {
  console.log("process.nextTick");
});

输出结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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() 之前连续执行。

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

参考

setImmediate() vs setTimeout() in JavaScript

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop
笔者以前面试的时候经常遇到写一堆setTimeout,setImmediate来问哪个先执行。本文主要就是来讲这个问题的,但是不是简单的讲讲哪个先,哪个后。笼统的知道setImmediate比setTimeout(fn, 0)先执行是不够的,因为有些情况下setTimeout(fn, 0)是会比setImmediate先执行的。要彻底搞明白这个问题,我们需要系统的学习JS的异步机制和底层原理。本文就会从异步基本概念出发,一直讲到Event Loop的底层原理,让你彻底搞懂setTimeout,setImmediate,Promise, process.nextTick谁先谁后这一类问题。
蒋鹏飞
2020/10/15
1.1K0
setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop
javascript事件循环
JavaScript 从一开始被创造出来就使用的单线程,这主要与他的用途相关。JavaScript主要用来与用户交互、操作网页上的dom元素等工作。
腾讯IVWEB团队
2020/06/29
1.4K0
javascript事件循环
详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
当时的我年轻气盛,在简历上放了自己的博客地址,而面试官应该是翻了我的博客,好几道面试题都是围绕着我的博文来提问
用户6256742
2024/06/22
5120
JavaScript Event Loop
JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。事件循环包含一个函数执行栈、一个宏任务队列、一个微任务队列。在说事件循环之前,需要说几个名词定义。
多云转晴
2020/03/26
1.4K0
JavaScript Event Loop
【THE LAST TIME】彻底吃透 JavaScript 执行机制
首先我们需要声明下,JavaScript 的执行和运行是两个不同概念的,执行,一般依赖于环境,比如 node、浏览器、Ringo 等, JavaScript 在不同环境下的执行机制可能并不相同。而今天我们要讨论的 Event Loop 就是 JavaScript 的一种执行方式。所以下文我们还会梳理 node 的执行方式。而运行呢,是指JavaScript 的解析引擎。这是统一的。
Nealyang
2019/09/29
4830
【THE LAST TIME】彻底吃透 JavaScript 执行机制
破阵九解:Node和浏览器之事件循环/任务队列/异步顺序/数据结构
浏览器中,涉及的异步API有:Promise, setTomeOut,setImmediate
啦啦啦321
2019/10/23
1.2K0
破阵九解:Node和浏览器之事件循环/任务队列/异步顺序/数据结构
JavaScript执行机制
JavaScript为什么是单线程的呢?由于设计之初,JavaScript是用来做用户交互以及页面动态渲染,所以为了简洁和方便入手,决定了它只能是单线程,否则将会带来非常复杂的同步问题。
lealc
2024/03/19
5832
JavaScript执行机制
JavaScript 面试要点: Event Loop (事件循环)
单线程意味着,JavaScript 在执行代码的任何时候,都只有一个主线程来处理所有的任务。非阻塞则是当代码需要进行一项异步任务时,主线程会挂起这个任务,然后在异步任务返回结果时再根据一定规则去执行相应回调。
Cellinlab
2023/05/17
7510
JavaScript 面试要点: Event Loop (事件循环)
JS运行机制
本文阐述了浏览器端和node端的js运行机制执行的过程,还进行了两者的运行机制比较,以及同步任务和异步任务的说明,两种异步任务的必要性,以及各自有哪些回调,部分回调的优先级。
ClyingDeng
2022/11/29
4.1K0
JS运行机制
Node中的事件循环和异步API
单线程编程会因阻塞I/O导致硬件资源得不到更优的使用。多线程编程也因为编程中的死锁、状态同步等问题让开发人员头痛。 Node在两者之间给出了它的解决方案:利用单线程,远离多线程死锁、状态同步等问题;利用异步I/O,让单线程远离阻塞,以好使用CPU。
前端下午茶
2018/10/22
1.7K0
Node中的事件循环和异步API
带你详细了解 Node.js 中的事件循环
Node.js 做为 JavaScript 的服务端运行时,主要与网络、文件打交道,没有了浏览器中事件循环的渲染阶段。
程序狗
2021/12/29
2.3K0
JavaScript事件循环机制解析
最近面试了很多家公司,这道题几乎是必被问到的一道题。之前总觉得自己了解得差不多,但是当第一次被问到的时候,却不知道该从哪里开始说起,涉及到的知识点很多。于是花时间整理了一下。并不仅仅是因为面试遇到了,而是理解JavaScript事件循环机制会让我们平常遇到的疑惑也得到解答。
前端迷
2020/07/16
7060
JavaScript事件循环机制解析
到底什么是Event Loop?那就来了解一下JavaScript分别在浏览器和Node环境下的运行机制吧
当时的我是一脸懵逼的,因为从来都没有听过这个专业名词。不过面试官还是很友好的,他说没关系,那你来做一道题,看看下面这段代码的执行结果是什么?
@零一
2021/01/29
7570
到底什么是Event Loop?那就来了解一下JavaScript分别在浏览器和Node环境下的运行机制吧
Node.js的事件循环(Event loop)、定时器(Timers)和 process.nextTick()
事件循环通过将操作分给系统内核来处理使得使用单线程的 JavaScript 的 Node.js 可以进行无阻塞 I/O 操作。
conanma
2022/01/05
1.6K0
面试题:说说事件循环机制(满分答案来了)
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
winty
2020/03/19
4.1K0
面试题:说说事件循环机制(满分答案来了)
一道面试题引发的事件循环深入思考
面试题如下,大家可以先试着写一下输出结果,看与正确答案是否有出入,如果大家不能准确的做出答案,可以通过下面对微任务,事件循环,定时器等相关代码执行顺序的讲解,让大家焕然一新。
coder_koala
2019/07/30
8310
一道面试题引发的事件循环深入思考
深度理解NodeJS事件循环
ALL THE TIME,我们写的的大部分javascript代码都是在浏览器环境下编译运行的,因此可能我们对浏览器的事件循环机制了解比Node.JS的事件循环更深入一些,但是最近写开始深入NodeJS学习的时候,发现NodeJS的事件循环机制和浏览器端有很大的区别,特此记录来深入的学习了下,以帮助自己及小伙伴们忘记后查阅及理解。
coder2028
2022/10/10
1K0
Event Loop(node.js)
常见的并发模型是并行工作者模型,任务分配给多个工作者,每个工作者完成整个任务,常说的 C 语言的多线程就是这种模型,它的工作模式如下图。
conanma
2022/01/04
8880
macrotask与microtask
最常见的延迟调用与间歇调用,Node环境的立即调用,高频的RAF,以及I/O操作和改UI。这些都是macrotask,事件循环的主要工作就是一轮一轮地检查macrotask queue,并处理这些任务
ayqy贾杰
2019/06/12
7330
JavaScript中的Event Loop机制详解(前端必看)
我们都知道,javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。
王小婷
2020/10/23
6290
推荐阅读
相关推荐
setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验