wordpress建好本地站怎么上傳聚合搜索引擎入口
setImmediate() vs setTimeout() 在 JavaScript 中的區(qū)別
在 JavaScript 中,setImmediate()
和 setTimeout()
都用于調(diào)度任務(wù),但它們的工作方式不同。
JavaScript 的異步特性
JavaScript 以其非阻塞、異步行為而聞名,尤其是在 Node.js 環(huán)境中。如果你曾經(jīng)參與過(guò)涉及定時(shí)器或回調(diào)的項(xiàng)目,你可能遇到過(guò) setTimeout()
,甚至 setImmediate()
。乍一看,這兩個(gè)函數(shù)似乎做的是同一件事——調(diào)度任務(wù)以便稍后運(yùn)行。但如果你曾經(jīng)一起運(yùn)行它們,你可能會(huì)注意到一些有趣的行為。
盡管它們的目的相似,但 setImmediate()
和 setTimeout()
在底層的操作方式不同。如果你想知道為什么 setImmediate()
回調(diào)似乎一個(gè)接一個(gè)地運(yùn)行,而 setTimeout()
回調(diào)則是間隔開(kāi)的,本指南將為你解析其中的原因。
這不僅僅是 JavaScript 的一個(gè)怪癖;它與 Node.js 如何管理異步任務(wù)密切相關(guān)。理解這兩個(gè)函數(shù)之間的差異將幫助你更好地控制代碼的時(shí)間和執(zhí)行順序,這對(duì)于大型應(yīng)用程序尤其重要,因?yàn)榧词故菚r(shí)間上的微小失誤也可能導(dǎo)致難以發(fā)現(xiàn)的錯(cuò)誤。
我們將深入探討事件循環(huán),它如何處理這些定時(shí)器,以及為什么在一起使用它們時(shí)事情并不總是按預(yù)期發(fā)生。到最后,你將更清楚地了解何時(shí)使用 setTimeout()
或 setImmediate()
,以滿足你所需的時(shí)間行為。
行為差異
setImmediate(() => {console.log("setImmediate 1");
});setTimeout(() => {console.log("setTimeout 1");
}, 0);setTimeout(() => {console.log("setTimeout 2");
}, 0);setImmediate(() => {console.log("setImmediate 2");
});
當(dāng)你運(yùn)行這段代碼時(shí),你可能期望 setTimeout
回調(diào)按定義的順序執(zhí)行,然后是 setImmediate
回調(diào)。但你在控制臺(tái)中看到的是:
setTimeout 1
setImmediate 1
setImmediate 2
setTimeout 2
如果這讓你感到困惑,不要擔(dān)心。讓我們解開(kāi)其中的原因。
事件循環(huán)
要理解這一點(diǎn),我們需要快速了解 Node.js 如何管理異步操作。Node.js 的異步特性核心是事件循環(huán)。
在 Node.js 中,事件循環(huán)處理不同的階段,每個(gè)階段負(fù)責(zé)執(zhí)行某些類型的回調(diào)。它幫助管理非阻塞任務(wù),確保函數(shù)可以異步執(zhí)行。在這些階段中,有不同的隊(duì)列。對(duì)于本次討論,有兩個(gè)隊(duì)列是重要的:
- 宏任務(wù)隊(duì)列:這是
setTimeout
和setImmediate
等任務(wù)所在的地方。 - 微任務(wù)隊(duì)列:這是 promises (
Promise.then()
) 和process.nextTick()
回調(diào)所在的地方。
事件循環(huán)的工作原理
要理解 setTimeout()
和 setImmediate()
的工作原理,我們需要看看 Node.js 中的事件循環(huán)。事件循環(huán)允許 Node.js 處理異步代碼。它在不同的階段處理不同類型的操作,每個(gè)階段負(fù)責(zé)特定的任務(wù)。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │└───────────────────────────┘
- 定時(shí)器階段:這是處理
setTimeout()
回調(diào)的地方。即使是 0 毫秒的延遲,它們也要等到下一次循環(huán)迭代才能執(zhí)行。 - 待處理回調(diào)階段:處理已完成的 I/O 事件,但我們的示例中沒(méi)有,所以跳過(guò)這個(gè)階段。
- 檢查階段:
setImmediate()
回調(diào)在這里運(yùn)行。它們?cè)?I/O 任務(wù)之后立即執(zhí)行,但在setTimeout()
回調(diào)之前。 - 輪詢階段:處理新的傳入 I/O 操作,如文件讀取或網(wǎng)絡(luò)請(qǐng)求。如果沒(méi)有 I/O,事件循環(huán)會(huì)跳過(guò)這個(gè)階段。
- 下一次循環(huán)迭代:在檢查階段之后,事件循環(huán)回到處理下一個(gè)定時(shí)器階段,在那里
setTimeout()
回調(diào)最終運(yùn)行。
setTimeout()
的 0 延遲
當(dāng)你使用 setTimeout()
并設(shè)置延遲為 0 時(shí),你實(shí)際上是在告訴 Node.js 在當(dāng)前操作完成后盡快運(yùn)行回調(diào)。然而,重要的是要記住,“盡快”仍然取決于事件循環(huán)的階段。
setTimeout(() => {console.log("setTimeout 1 with 0 delay");
}, 0);setImmediate(() => {console.log("setImmediate 1");
});setTimeout(() => {console.log("setTimeout 2 with 0 delay");
}, 0);
輸出結(jié)果:
setTimeout 1 with 0 delay
setImmediate 1
setTimeout 2 with 0 delay
即使延遲為 0,setTimeout()
回調(diào)仍然需要等待定時(shí)器階段的下一次循環(huán),因此不會(huì)立即運(yùn)行。相反,它被放置在宏任務(wù)隊(duì)列中,以便在下一個(gè)可用機(jī)會(huì)執(zhí)行。
setImmediate()
另一方面,setImmediate()
設(shè)計(jì)用于在 I/O 事件完成后執(zhí)行回調(diào),在同一事件循環(huán)迭代中。這意味著 setImmediate()
回調(diào)在額外的定時(shí)器(如 setTimeout()
)執(zhí)行之前被處理,特別是在沒(méi)有 I/O 的情況下。
在我們的示例中,由于沒(méi)有 I/O 發(fā)生,兩個(gè) setImmediate()
回調(diào)會(huì)一個(gè)接一個(gè)地執(zhí)行,然后才輪到第二個(gè) setTimeout()
回調(diào)。
為什么 setImmediate
回調(diào)會(huì)一起運(yùn)行?
- 相同的事件循環(huán)周期:兩個(gè)
setImmediate
調(diào)用在事件循環(huán)的同一個(gè)周期(或循環(huán))中被放置到宏任務(wù)隊(duì)列中。Node.js 按順序處理這些任務(wù)。 - 優(yōu)先于
setTimeout()
:即使setTimeout()
設(shè)定了 0 延遲,這也不保證立即執(zhí)行。setImmediate()
回調(diào)在當(dāng)前周期中優(yōu)先于setTimeout()
任務(wù)。
現(xiàn)實(shí)世界的類比
想象一下在餐館點(diǎn)餐和飲料。
- 你點(diǎn)了一道菜(代表
setTimeout(0)
)。 - 廚師將其添加到訂單隊(duì)列中,一旦準(zhǔn)備好就會(huì)送達(dá)。
- 同時(shí),你要了一杯水(
setImmediate()
),由于它快速且容易準(zhǔn)備,服務(wù)員會(huì)在你的食物完成之前立即送達(dá)。
在這個(gè)類比中,水(快速任務(wù))首先被處理,即使兩個(gè)訂單幾乎同時(shí)下達(dá)。菜(稍微復(fù)雜一些)稍后送達(dá)。
這種情況總是發(fā)生嗎?
不一定。setImmediate()
和 setTimeout()
的行為可能取決于代碼中發(fā)生的其他異步操作。如果有 I/O 操作,執(zhí)行順序可能會(huì)改變,因?yàn)?setImmediate()
只會(huì)在 I/O 事件完成后運(yùn)行。
const fs = require("fs");fs.readFile("example.txt", () => {setTimeout(() => {console.log("setTimeout after I/O");}, 0);setImmediate(() => {console.log("setImmediate after I/O");});
});
輸出結(jié)果:
setImmediate after I/O
setTimeout after I/O
在這種情況下,setImmediate()
總是在 setTimeout()
之前運(yùn)行,因?yàn)槭录h(huán)在 I/O 回調(diào)之后優(yōu)先處理 setImmediate()
。
當(dāng)沒(méi)有 I/O 事件時(shí),兩個(gè) setImmediate()
回調(diào)會(huì)一個(gè)接一個(gè)地運(yùn)行,然后才輪到 setTimeout()
回調(diào)。
process.nextTick()
和 Promises
以下示例展示了 Node.js 中各種異步操作的處理方式:
setTimeout(() => {console.log("setTimeout");
}, 0);setImmediate(() => {console.log("setImmediate");
});Promise.resolve().then(() => {console.log("Promise then");
});process.nextTick(() => {console.log("process.nextTick");
});
輸出結(jié)果:
process.nextTick
Promise then
setTimeout
setImmediate
process.nextTick()
:這將在任何其他任務(wù)之前運(yùn)行,甚至在微任務(wù)(如 Promises)之前。Promise.then()
:這是一個(gè)微任務(wù),因此它在當(dāng)前操作之后但在宏任務(wù)(如setTimeout()
和setImmediate()
)之前運(yùn)行。setTimeout()
:在微任務(wù)處理完之后運(yùn)行。setImmediate()
:盡管它類似于setTimeout()
,但它在事件循環(huán)周期的后期運(yùn)行,在當(dāng)前 I/O 操作之后。
Node.js 的異步行為有時(shí)可能會(huì)令人困惑,特別是在處理 setTimeout()
和 setImmediate()
時(shí)。關(guān)鍵是理解事件循環(huán)以及任務(wù)在不同階段的調(diào)度方式。
setImmediate()
在 I/O 事件之后和當(dāng)前事件循環(huán)周期內(nèi)運(yùn)行。setTimeout()
在指定的延遲之后運(yùn)行,即使延遲為 0,它也會(huì)為下一次事件循環(huán)迭代調(diào)度任務(wù)。- 當(dāng)沒(méi)有 I/O 操作時(shí),
setImmediate()
會(huì)在下一個(gè)setTimeout()
之前連續(xù)執(zhí)行。
理解這些差異有助于你精確控制代碼的運(yùn)行時(shí)間,這在高性能應(yīng)用程序中至關(guān)重要,因?yàn)闀r(shí)間和效率非常重要。