Node.js事件循環

事件循環是了解節點最重要的方面之一

介紹

事件循環是有關Node的最重要方面之一。

為什麼這個這麼重要?因為它說明了Node如何實現異步並具有無阻塞的I / O,所以它基本上說明了Node的“殺手級應用”,這是使它成功的原因。

Node.js JavaScript代碼在單個線程上運行。一次只發生一件事。

這個限制實際上非常有用,因為它大大簡化了您的編程方式,而不必擔心並發問題。

您只需要注意如何編寫代碼,並避免任何可能阻塞線程的事情,例如同步網絡調用或無限循環

通常,在大多數瀏覽器中,每個瀏覽器選項卡都有一個事件循環,以使每個進程都隔離開,並避免出現無限循環或繁重的處理過程的網頁來阻塞您的整個瀏覽器。

該環境管理多個並發事件循環,以處理例如API調用。網絡工作者也可以在自己的事件循環中運行。

您主要需要擔心的是您的代碼將在單個事件循環上運行,並且在編寫代碼時要牢記這一點,以避免阻塞它。

阻止事件循環

任何花費很長時間才能將控制權返回到事件循環的JavaScript代碼,都將阻止頁面中任何JavaScript代碼的執行,甚至阻止UI線程,並且用戶無法單擊瀏覽,滾動頁面等。

JavaScript中幾乎所有的I / O原語都是非阻塞的。網絡請求,文件系統操作等。被阻塞是一個例外,這就是為什麼JavaScript如此多地基於回調,最近才基於諾言異步/等待

調用堆棧

調用堆棧是一個LIFO隊列(後進先出)。

事件循環不斷檢查調用堆棧看看是否有任何需要運行的功能。

這樣做時,它將找到的所有函數調用添加到調用堆棧中,並按順序執行每個函數。

您知道在調試器或瀏覽器控制台中可能熟悉的錯誤堆棧跟踪嗎?瀏覽器在調用堆棧中查找函數名稱,以通知您哪個函數發起了當前調用:

Exception call stack

一個簡單的事件循環說明

讓我們舉個例子:

我用foobarbaz作為隨機名稱。輸入任何名稱以替換它們。

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) bar() baz() }

foo()

此代碼打印

foo
bar
baz

如預期的那樣。

當此代碼運行時,首先foo()叫做。裡面foo()我們先打電話bar(),然後我們致電baz()

此時,調用堆棧如下所示:

Call stack first example

每次迭代中的事件循環都會檢查調用堆棧中是否有東西,並執行它:

Execution order first example

直到調用堆棧為空。

排隊功能執行

上面的示例看起來很正常,沒有什麼特別的:JavaScript查找要執行的事物,並按順序運行它們。

讓我們看看如何將函數推遲到堆棧清除之前。

用例setTimeout(() => {}), 0)就是調用一個函數,但是一旦代碼中的每個其他函數都執行了,就執行它。

舉個例子:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) baz() }

foo()

這段代碼打印出來,也許令人驚訝:

foo
baz
bar

運行此代碼時,將首先調用foo()。在foo()內部,我們首先調用setTimeout,bar作為參數,我們指示它盡可能快地運行,並傳遞0作為計時器。然後我們調用baz()。

此時,調用堆棧如下所示:

Call stack second example

這是程序中所有功能的執行順序:

Execution order second example

為什麼會這樣呢?

消息隊列

調用setTimeout()時,Browser或Node.js將啟動計時器。一旦計時器到期,在這種情況下,我們立即將超時值設為0,則將回調函數放入消息隊列

消息隊列也是用戶啟動的事件,例如單擊或鍵盤事件,或者拿來在您的代碼有機會對它們進行響應之前,對響應進行排隊。或者也DOM像這樣的事件onLoad

循環將優先級賦予調用堆棧,它首先處理在調用堆棧中找到的所有內容,一旦其中沒有任何內容,便開始處理消息隊列中的內容。

我們不必等待諸如setTimeout,抓取或其他方式來完成自己的工作,因為它們是由瀏覽器提供的,並且它們位於自己的線程中。例如,如果您設置了setTimeout超時為2秒,您不必等待2秒-等待發生在其他地方。

ES6作業隊列

ECMAScript 2015引入了作業隊列的概念,Promises使用了該隊列(也在ES6 / ES2015中引入)。這是一種盡快執行異步函數結果的方法,而不是放在調用堆棧的末尾。

在當前功能結束之前解決的承諾將在當前功能之後立即執行。

我發現在遊樂園中過山車的類比很好:消息隊列將您排在隊列的後面,在所有其他人的後面,您將不得不等待轉彎,而工作隊列是快速通道票這樣您就可以在完成上一個騎行之後立即騎另一個騎行。

例子:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) new Promise((resolve, reject) => resolve(‘should be right after baz, before bar’) ).then(resolve => console.log(resolve)) baz() }

foo()

此打印

foo
baz
should be right after baz, before bar
bar

這是Promises(和基於Promise構建的Async / await)與通過setTimeout()或其他平台API。

結論

本文向您介紹了Node.js事件循環的基本構建塊。

這是用Node.js編寫的任何程序的重要組成部分,我希望這裡介紹的某些概念將來對您有用。

免費下載我的Node.js手冊


更多節點教程: