了解nodejs中的事件和事件循環
相關推薦:《nodejs 教學》
#熟悉javascript的朋友應該都使用過事件,例如滑鼠的移動,滑鼠的點擊,鍵盤的輸入等等。我們在javascript中監聽這些事件,從而觸發對應的處理。
同樣的nodejs中也有事件,而且還有一個專門的events模組來進行專門的處理。
同時事件和事件循環也是nodejs建構非同步IO的非常重要的概念。
今天我們來詳細了解一下。
事件
nodejs為事件提供了一個專門的模組:lib/events.js。
還記得我們在講使用nodejs建立web伺服器嗎?
const server = http.createServer((req, res) => { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end('welcome to www.flydean.com\n') })
這裡,每個請求都會觸發request事件。
nodejs的核心API是基於非同步事件驅動程式來進行架構的,所以nodejs中有非常多的事件。
例如:net.Server 會在每次有新連線時觸發事件,fs.ReadStream 會在開啟檔案時觸發事件,stream會在資料可讀時觸發事件。
我們來看怎麼建構一個nodejs的事件:
const EventEmitter = require('events') const eventEmitter = new EventEmitter()
events常用的方法有兩個,分別是on和emit。
on用來監聽事件,emit用來觸發事件。
eventEmitter.on('fire', () => { console.log('开火') }) eventEmitter.emit('fire')
emit也可以帶參數,我們看下一個參數的情況:
eventEmitter.on('fire', who => { console.log(`开火 ${who}`) }) eventEmitter.emit('fire', '美帝')
再看看兩個參數的情況:
eventEmitter.on('fire', (who, when) => { console.log(`开火 ${who} ${when}`) }) eventEmitter.emit('fire', '川建国','now')
預設情況下,EventEmitter以註冊的順序同步地呼叫所有監聽器。這樣可以確保事件的正確排序,並有助於避免競態條件和邏輯錯誤。
如果需要非同步執行,則可以使用setImmediate() 或 process.nextTick()來切換到非同步執行模式。
eventEmitter.on('fire', (who, when) => { setImmediate(() => { console.log(`开火 ${who} ${when}`); }); }) eventEmitter.emit('fire', '川建国','now')
除此之外,events還支援其他幾個方法:
once(): 新增單次監聽器
removeListener() / off(): 從事件中移除事件監聽器
removeAllListeners(): 移除事件的所有監聽器
事件循環
我們知道nodejs的程式碼是運行在單執行緒環境中的,每次只會去處理一件事情。
這一種處理方式,避免了多執行緒環境的資料同步的問題,大大的提升了處理效率。
所謂事件循環,就是指處理器在一個程式週期中,處理完這個週期的事件之後,會進入下一個事件週期,處理下一個事件週期的事情,這樣一個週期一個週期的循環。
事件循環的阻塞
如果我們在事件處理過程中,某個事件的處理髮生了阻塞,則會影響其他的事件的執行,所以我們可以看到在JS中,幾乎所有的IO都是非阻塞的。這也是為什麼javascript有這麼多回呼的原因。
事件循環範例
我們看一個簡單的事件循環的例子:
const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') action2() action3() } action1()
上面的程式碼輸出:
action1 action2 action3
堆疊和訊息佇列
我們知道函數間的呼叫是透過堆疊來實現的,上面的例子中,我們的呼叫順序也是透過堆疊來實現的。
但不是函數中所有的方法都會入棧,還有一些方法會被放入訊息佇列。
我們再舉一個例子:
const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') setTimeout(action2, 0) action3() } action1()
上面的程式碼運行結果:
action1 action3 action2
結果不一樣了。這是因為settimeout觸發了定時器,當定時器到期的時候,回呼函數會被放入訊息佇列中等待被處理,而不是放入堆疊中。
事件循環會優先處理堆疊中的事件,只有堆疊中沒有任何資料的時候,才會去轉而消費訊息佇列中的事件。
雖然上面範例中setTimeout的timeout時間是0,但還是要等到action3執行完畢才能執行。
注意,setTimeout中的timeout並不是在目前的執行緒進行等待的,它是由瀏覽器或其他JS執行環境來呼叫。
作業佇列和promise
ES6中的Promise引入了作業佇列的概念,使用作業佇列將會盡快執行非同步函數的結果,而不是放在呼叫堆疊的末端。
舉例:
const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') setTimeout(action2, 0) new Promise((resolve, reject) => resolve('应该在action3之后、action2之前') ).then(resolve => console.log(resolve)) action3() } action1()
輸出結果:
action1 action3 应该在action3之后、action2之前 action2
這是因為,在目前函數結束之前 resolve 的 Promise 會在目前函數之後立即執行。
也就是說先執行堆疊,再執行作業佇列,最後執行訊息佇列。
process.nextTick()
先給大家一個定義叫做tick,一個tick就是指一個事件週期。而process.nextTick()就是指在下一個事件循環tick開始之前,呼叫這個函數:
process.nextTick(() => { console.log('i am the next tick'); })
所以nextTick一定要比訊息佇列的setTimeout快。
setImmediate()
nodejs提供了一個setImmediate方法,來盡快的執行程式碼。
setImmediate(() => { console.log('I am immediate!'); })
setImmediate中的函數會在事件循環的下一個迭代中執行。
setImmediate() 和 setTimeout(() => {}, 0)的功能基本上是類似的。它們都會在事件循環的下一個迭代中運行。
setInterval()
如果想要定時執行某些回呼函數,則需要使用到setInterval。
setInterval(() => { console.log('每隔2秒执行一次'); }, 2000)
要清除上面的定时任务,可以使用clearInterval:
const id = setInterval(() => { console.log('每隔2秒执行一次'); }, 2000) clearInterval(id)
注意,setInterval是每隔n毫秒启动一个函数,不管该函数是否执行完毕。
如果一个函数执行时间太长,就会导致下一个函数同时执行的情况,怎么解决这个问题呢?
我们可以考虑在回调函数内部再次调用setTimeout,这样形成递归的setTimeout调用:
const myFunction = () => { console.log('做完后,隔2s再次执行!'); setTimeout(myFunction, 2000) } setTimeout(myFunction, 2000)
更多编程相关知识,请访问:编程视频!!
以上是了解nodejs中的事件和事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

摘要:C++中的非同步程式設計允許多工處理,無需等待耗時操作。使用函數指標建立指向函數的指標。回調函數在非同步操作完成時被呼叫。 boost::asio等函式庫提供非同步程式支援。實戰案例示範如何使用函數指標和boost::asio實現非同步網路請求。

並發程式設計中的事件驅動機制透過在事件發生時執行回呼函數來回應外部事件。在C++中,事件驅動機制可用函數指標實作:函數指標可以註冊回呼函數,在事件發生時執行。 lambda表達式也可以實現事件回調,允許建立匿名函數物件。實戰案例使用函數指標實作GUI按鈕點擊事件,在事件發生時呼叫回呼函數並列印訊息。

Java框架非同步程式設計中常見的3個問題和解決方案:回呼地獄:使用Promise或CompletableFuture以更直覺的風格管理回呼。資源競爭:使用同步原語(如鎖)保護共享資源,並考慮使用執行緒安全性集合(如ConcurrentHashMap)。未處理異常:明確處理任務中的異常,並使用異常處理框架(如CompletableFuture.exceptionally())處理異常。

Go框架利用Go的並發和非同步特性提供高效處理並發和非同步任務的機制:1.透過Goroutine實現並發,允許同時執行多個任務;2.透過通道實現非同步編程,在不阻塞主執行緒的情況下執行任務;3.適用於實戰場景,如並發處理HTTP請求、非同步取得資料庫資料等。

jQuery是一個受歡迎的JavaScript函式庫,可以用來簡化DOM操作、事件處理、動畫效果等。在web開發中,常常會遇到需要對select元素進行改變事件綁定的情況。本文將介紹如何使用jQuery實作對select元素改變事件的綁定,並提供具體的程式碼範例。首先,我們需要使用標籤來建立一個包含選項的下拉式選單:

非同步程式設計在PHP的優勢包括更高的吞吐量、更低的延遲、更好的資源利用和可擴展性。其劣勢包括複雜性、調試難度和有限的庫支援。在實戰案例中,ReactPHP用於處理WebSocket連接,展示了非同步程式設計的實際應用。

1.為什麼要使用非同步程式設計?傳統程式設計使用阻塞式I/O,這表示程式會等待某個操作完成,然後才能繼續執行。這對於處理單一任務可能很有效,但對於處理大量任務時,可能會導致程式變慢。非同步程式設計則打破了傳統阻塞式I/O的限制,它使用非阻塞式I/O,這意味著程式可以將任務分發到不同的執行緒或事件循環中執行,而無需等待任務完成。這允許程式同時處理多個任務,提高程式的效能和效率。 2.python非同步程式設計的基礎Python非同步程式設計的基礎是協程和事件循環。協程是允許函數在暫停和恢復之間切換的函數。事件循環則負責調度

在PHP中建構基於事件的應用程式的方法包括:使用EventSourceAPI建立事件來源,並在客戶端使用EventSource物件監聽事件。使用伺服器傳送的事件(SSE)傳送事件,並在客戶端使用XMLHttpRequest物件監聽事件。一個實用的例子是在電子商務網站中使用EventSource即時更新庫存計數,在伺服器端透過隨機更改庫存並發送更新來實現,客戶端則透過EventSource監聽庫存更新並即時顯示。
