這篇文章給大家分享的是有關(guān)Node.js中回調(diào)隊(duì)列的案例分析的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。
為白云等地區(qū)用戶提供了全套網(wǎng)頁(yè)設(shè)計(jì)制作服務(wù),及白云網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為網(wǎng)站建設(shè)、做網(wǎng)站、白云網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!隊(duì)列是 Node.js 中用于有效處理異步操作的一項(xiàng)重要技術(shù)。
在本文中,我們將深入研究 Node.js 中的隊(duì)列:它們是什么,它們?nèi)绾喂ぷ鳎ㄍㄟ^(guò)事件循環(huán))以及它們的類型。
隊(duì)列是 Node.js 中用于組織異步操作的數(shù)據(jù)結(jié)構(gòu)。這些操作以不同的形式存在,包括HTTP請(qǐng)求、讀取或?qū)懭胛募僮?、流等?/p>
在 Node.js 中處理異步操作非常具有挑戰(zhàn)性。
HTTP 請(qǐng)求期間可能會(huì)出現(xiàn)不可預(yù)測(cè)的延遲(或者更糟糕的可能性是沒(méi)有結(jié)果),具體取決于網(wǎng)絡(luò)質(zhì)量。嘗試用 Node.js 讀寫(xiě)文件時(shí)也有可能會(huì)產(chǎn)生延遲,具體取決于文件的大小。
類似于計(jì)時(shí)器和其他的許多操作,異步操作完成的時(shí)間也有可能是不確定的。
在這些不同的延遲情況之下,Node.js 需要能夠有效地處理所有這些操作。
Node.js 無(wú)法處理基于 first-start-first-handle (先開(kāi)始先處理)或 first-finish-first-handle (先結(jié)束先處理)的操作。
之所以不能這樣做的一個(gè)原因是,在一個(gè)異步操作中可能還會(huì)包含另一個(gè)異步操作。
為第一個(gè)異步過(guò)程留出空間意味著必須先要完成內(nèi)部異步過(guò)程,然后才能考慮隊(duì)列中的其他異步操作。
有許多情況需要考慮,因此最好的選擇是制定規(guī)則。這個(gè)規(guī)則影響了事件循環(huán)和隊(duì)列在 Node.js 中的工作方式。
讓我們簡(jiǎn)要地看一下 Node.js 是怎樣處理異步操作的。
調(diào)用棧被用于跟蹤當(dāng)前正在執(zhí)行的函數(shù)以及從何處開(kāi)始運(yùn)行。當(dāng)一個(gè)函數(shù)將要執(zhí)行時(shí),它會(huì)被添加到調(diào)用堆棧中。這有助于 JavaScript 在執(zhí)行函數(shù)后重新跟蹤其處理步驟。
回調(diào)隊(duì)列是在后臺(tái)操作完成時(shí)把回調(diào)函數(shù)保存為異步操作的隊(duì)列。它們以先進(jìn)先出(FIFO)的方式工作。我們將會(huì)在本文后面介紹不同類型的回調(diào)隊(duì)列。
請(qǐng)注意,Node.js 負(fù)責(zé)所有異步活動(dòng),因?yàn)?JavaScript 可以利用其單線程性質(zhì)來(lái)阻止產(chǎn)生新的線程。
在完成后臺(tái)操作后,它還負(fù)責(zé)向回調(diào)隊(duì)列添加函數(shù)。 JavaScript 本身與回調(diào)隊(duì)列無(wú)關(guān)。同時(shí)事件循環(huán)會(huì)連續(xù)檢查調(diào)用棧是否為空,以便可以從回調(diào)隊(duì)列中提取一個(gè)函數(shù)并添加到調(diào)用棧中。事件循環(huán)僅在執(zhí)行所有同步操作之后才檢查隊(duì)列。
那么,事件循環(huán)是按照什么樣的順序從隊(duì)列中選擇回調(diào)函數(shù)的呢?
首先,讓我們看一下回調(diào)隊(duì)列的五種主要類型。
IO操作是指涉及外部設(shè)備(如計(jì)算機(jī)的硬盤(pán)、網(wǎng)卡等)的操作。常見(jiàn)的操作包括讀寫(xiě)文件操作、網(wǎng)絡(luò)操作等。這些操作應(yīng)該是異步的,因?yàn)樗鼈兞艚o Node.js 處理。
JavaScript 無(wú)法訪問(wèn)計(jì)算機(jī)的內(nèi)部設(shè)備。當(dāng)執(zhí)行此類操作時(shí),JavaScript 會(huì)將其傳輸?shù)?Node.js 以在后臺(tái)處理。
完成后,它們將會(huì)被轉(zhuǎn)移到 IO 回調(diào)隊(duì)列中,來(lái)進(jìn)行事件循環(huán),以轉(zhuǎn)移到調(diào)用棧中執(zhí)行。
每個(gè)涉及 Node.js 計(jì)時(shí)器功能的操作(如 setTimeout()
和 setInterval()
)都是要被添加到計(jì)時(shí)器隊(duì)列的。
請(qǐng)注意,JavaScript 語(yǔ)言本身沒(méi)有計(jì)時(shí)器功能。它使用 Node.js 提供的計(jì)時(shí)器 API(包括 setTimeout
)執(zhí)行與時(shí)間相關(guān)的操作。所以計(jì)時(shí)器操作是異步的。無(wú)論是 2 秒還是 0 秒,JavaScript 都會(huì)把與時(shí)間相關(guān)的操作移交給 Node.js,然后將其完成并添加到計(jì)時(shí)器隊(duì)列中。
例如:
setTimeout(function() { console.log('setTimeout'); }, 0) console.log('yeah') # 返回 yeah setTimeout
在處理異步操作時(shí),JavaScript 會(huì)繼續(xù)執(zhí)行其他操作。只有在所有同步操作都已被處理完畢后,事件循環(huán)才會(huì)進(jìn)入回調(diào)隊(duì)列。
該隊(duì)列分為兩個(gè)隊(duì)列:
process.nextTick
函數(shù)而延遲的函數(shù)。事件循環(huán)執(zhí)行的每個(gè)迭代稱為一個(gè) tick(時(shí)間刻度)。
process.nextTick
是一個(gè)函數(shù),它在下一個(gè) tick (即事件循環(huán)的下一個(gè)迭代)執(zhí)行一個(gè)函數(shù)。微任務(wù)隊(duì)列需要存儲(chǔ)此類函數(shù),以便可以在下一個(gè) tick 執(zhí)行它們。
這意味著事件循環(huán)必須繼續(xù)檢查微任務(wù)隊(duì)列中的此類函數(shù),然后再進(jìn)入其他隊(duì)列。
promises
而延遲的函數(shù)。如你所見(jiàn),在 IO 和計(jì)時(shí)器隊(duì)列中,所有與異步操作有關(guān)的內(nèi)容都被移交給了異步函數(shù)。
但是 promise 不同。在 promise 中,初始變量存儲(chǔ)在 JavaScript 內(nèi)存中(你可能已經(jīng)注意到了
)。
異步操作完成后,Node.js 會(huì)將函數(shù)(附加到 Promise)放在微任務(wù)隊(duì)列中。同時(shí)它用得到的結(jié)果來(lái)更新 JavaScript 內(nèi)存中的變量,以使該函數(shù)不與
一起運(yùn)行。
以下代碼說(shuō)明了 promise 是如何工作的:
let prom = new Promise(function (resolve, reject) { // 延遲執(zhí)行 setTimeout(function () { return resolve("hello"); }, 2000); }); console.log(prom); // Promise {} prom.then(function (response) { console.log(response); }); // 在 2000ms 之后,輸出 // hello
關(guān)于微任務(wù)隊(duì)列,需要注意一個(gè)重要功能,事件循環(huán)在進(jìn)入其他隊(duì)列之前要反復(fù)檢查并執(zhí)行微任務(wù)隊(duì)列中的函數(shù)。例如,當(dāng)微任務(wù)隊(duì)列完成時(shí),或者說(shuō)計(jì)時(shí)器操作執(zhí)行了 Promise 操作,事件循環(huán)將會(huì)在繼續(xù)進(jìn)入計(jì)時(shí)器隊(duì)列中的其他函數(shù)之前參與該 Promise 操作。
因此,微任務(wù)隊(duì)列比其他隊(duì)列具有最高的優(yōu)先級(jí)。
檢查隊(duì)列也稱為即時(shí)隊(duì)列(immediate queue)。IO 隊(duì)列中的所有回調(diào)函數(shù)均已執(zhí)行完畢后,立即執(zhí)行此隊(duì)列中的回調(diào)函數(shù)。setImmediate
用于向該隊(duì)列添加函數(shù)。
例如:
const fs = require('fs'); setImmediate(function() { console.log('setImmediate'); }) // 假設(shè)此操作需要 1ms fs.readFile('path-to-file', function() { console.log('readFile') }) // 假設(shè)此操作需要 3ms do...while...
執(zhí)行該程序時(shí),Node.js 把 setImmediate
回調(diào)函數(shù)添加到檢查隊(duì)列。由于整個(gè)程序尚未準(zhǔn)備完畢,因此事件循環(huán)不會(huì)檢查任何隊(duì)列。
因?yàn)?readFile
操作是異步的,所以會(huì)移交給 Node.js,之后程序?qū)?huì)繼續(xù)執(zhí)行。
do while
操作持續(xù) 3ms。在這段時(shí)間內(nèi),readFile
操作完成并被推送到 IO 隊(duì)列。完成此操作后,事件循環(huán)將會(huì)開(kāi)始檢查隊(duì)列。
盡管首先填充了檢查隊(duì)列,但只有在 IO 隊(duì)列為空之后才考慮使用它。所以在 setImmediate
之前,將 readFile
輸出到控制臺(tái)。
此隊(duì)列存儲(chǔ)與關(guān)閉事件操作關(guān)聯(lián)的函數(shù)。
包括以下內(nèi)容:
這些隊(duì)列被認(rèn)為是優(yōu)先級(jí)最低的,因?yàn)榇颂幍牟僮鲿?huì)在以后發(fā)生。
你肯sing不希望在處理 promise 函數(shù)之前在 close 事件中執(zhí)行回調(diào)函數(shù)。當(dāng)服務(wù)器已經(jīng)關(guān)閉時(shí),promise 函數(shù)會(huì)做些什么呢?
微任務(wù)隊(duì)列具有最高優(yōu)先級(jí),其次是計(jì)時(shí)器隊(duì)列,I/O隊(duì)列,檢查隊(duì)列,最后是關(guān)閉隊(duì)列。
讓我們通過(guò)一個(gè)更復(fù)雜的例子來(lái)說(shuō)明隊(duì)列的類型和順序:
const fs = require("fs"); // 假設(shè)此操作需要 2ms fs.writeFile('./new-file.json', '...', function() { console.log('writeFile') }) // 假設(shè)這需要 10ms 才能完成 fs.readFile("./file.json", function(err, data) { console.log("readFile"); }); // 不需要假設(shè),這實(shí)際上需要 1ms setTimeout(function() { console.log("setTimeout"); }, 1000); // 假設(shè)此操作需要 3ms while(...) { ... } setImmediate(function() { console.log("setImmediate"); }); // 解決 promise 需要 4 ms let promise = new Promise(function (resolve, reject) { setTimeout(function () { return resolve("promise"); }, 4000); }); promise.then(function(response) { console.log(response) }) console.log("last line");
程序流程如下:
fs.writeFile
在后臺(tái)花費(fèi) 2 毫秒。fs.readFile
takes 10ms at the background before Node.js adds the callback function to the IO queue.
fs.readFile
在后臺(tái)花費(fèi) 10 毫秒。setTimeout
在后臺(tái)花費(fèi) 1ms。setTimeout
和 fs.writeFile
操作完成,并將它們的回調(diào)函數(shù)分別添加到計(jì)時(shí)器和 IO 隊(duì)列中。現(xiàn)在的隊(duì)列是:
// queues Timer = [ function () { console.log("setTimeout"); }, ]; IO = [ function () { console.log("writeFile"); }, ];
setImmediate
將回調(diào)函數(shù)添加到 Check 隊(duì)列中:
js // 隊(duì)列 Timer... IO... Check = [ function() {console.log("setImmediate")} ]
在將 promise 操作添加到微任務(wù)隊(duì)列之前,需要花費(fèi) 4ms 的時(shí)間在后臺(tái)進(jìn)行解析。
最后一行是同步的,因此將會(huì)立即執(zhí)行:
# 返回 "last line"
因?yàn)樗型交顒?dòng)都已完成,所以事件循環(huán)開(kāi)始檢查隊(duì)列。由于微任務(wù)隊(duì)列為空,因此它從計(jì)時(shí)器隊(duì)列開(kāi)始:
// 隊(duì)列 Timer = [] // 現(xiàn)在是空的 IO... Check... # 返回 "last line" "setTimeout"
當(dāng)事件循環(huán)繼續(xù)執(zhí)行隊(duì)列中的回調(diào)函數(shù)時(shí),promise
操作完成并被添加到微任務(wù)隊(duì)列中:
// 隊(duì)列 Timer = []; Microtask = [ function (response) { console.log(response); }, ]; IO = []; // 當(dāng)前是空的 Check = []; // 當(dāng)前是在 IO 的后面,為空 # results "last line" "setTimeout" "writeFile" "setImmediate"
幾秒鐘后,readFile
操作完成,并添加到 IO 隊(duì)列中:
// 隊(duì)列 Timer = []; Microtask = []; // 當(dāng)前是空的 IO = [ function () { console.log("readFile"); }, ]; Check = []; # results "last line" "setTimeout" "writeFile" "setImmediate" "promise"
最后,執(zhí)行所有回調(diào)函數(shù):
// 隊(duì)列 Timer = [] Microtask = [] IO = [] // 現(xiàn)在又是空的 Check = []; # results "last line" "setTimeout" "writeFile" "setImmediate" "promise" "readFile"
這里要注意的三點(diǎn):
readFile
),事件循環(huán)也會(huì)執(zhí)行檢查隊(duì)列中的函數(shù)。這樣做的原因是此時(shí) IO 隊(duì)列為空。請(qǐng)記住,在執(zhí)行 IO 隊(duì)列中的所有的函數(shù)之后,將會(huì)立即運(yùn)行檢查隊(duì)列回調(diào)。JavaScript 是單線程的。每個(gè)異步函數(shù)都由依賴操作系統(tǒng)內(nèi)部函數(shù)工作的 Node.js 去處理。
Node.js 負(fù)責(zé)將回調(diào)函數(shù)(通過(guò) JavaScript 附加到異步操作)添加到回調(diào)隊(duì)列中。事件循環(huán)會(huì)確定將要在每次迭代中接下來(lái)要執(zhí)行的回調(diào)函數(shù)。
了解隊(duì)列如何在 Node.js 中工作,使你對(duì)其有了更好的了解,因?yàn)殛?duì)列是環(huán)境的核心功能之一。 Node.js 最受歡迎的定義是 non-blocking
(非阻塞),這意味著異步操作可以被正確的處理。都是因?yàn)橛辛耸录h(huán)和回調(diào)隊(duì)列才能使此功能生效。
感謝各位的閱讀!關(guān)于Node.js中回調(diào)隊(duì)列的案例分析就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!