1. 程式人生 > >nodejs事件和事件迴圈簡介

nodejs事件和事件迴圈簡介

[toc] # 簡介 熟悉javascript的朋友應該都使用過事件,比如滑鼠的移動,滑鼠的點選,鍵盤的輸入等等。我們在javascript中監聽這些事件,從而觸發相應的處理。 同樣的nodejs中也有事件,並且還有一個專門的events模組來進行專門的處理。 同時事件和事件迴圈也是nodejs構建非同步IO的非常重要的概念。 今天我們來詳細瞭解一下。 # 事件 nodejs為事件提供了一個專門的模組:lib/events.js。 還記得我們在講使用nodejs構建web伺服器嗎? ~~~js 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的事件: ~~~js const EventEmitter = require('events') const eventEmitter = new EventEmitter() ~~~ events常用的方法有兩個,分別是on和emit。 on用來監聽事件,emit用來觸發事件。 ~~~js eventEmitter.on('fire', () => { console.log('開火') }) eventEmitter.emit('fire') ~~~ emit還可以帶引數,我們看下一個引數的情況: ~~~js eventEmitter.on('fire', who => { console.log(`開火 ${who}`) }) eventEmitter.emit('fire', '美帝') ~~~ 再看看兩個引數的情況: ~~~js eventEmitter.on('fire', (who, when) => { console.log(`開火 ${who} ${when}`) }) eventEmitter.emit('fire', '川建國','now') ~~~ 預設情況下,EventEmitter以註冊的順序同步地呼叫所有監聽器。這樣可以確保事件的正確排序,並有助於避免競態條件和邏輯錯誤。 如果需要非同步執行,則可以使用setImmediate() 或者 process.nextTick()來切換到非同步執行模式。 ~~~js eventEmitter.on('fire', (who, when) => { setImmediate(() => { console.log(`開火 ${who} ${when}`); }); }) eventEmitter.emit('fire', '川建國','now') ~~~ 除此之外,events還支援其他幾個方法: once(): 新增單次監聽器 removeListener() / off(): 從事件中移除事件監聽器 removeAllListeners(): 移除事件的所有監聽器 # 事件迴圈 我們知道nodejs的程式碼是執行在單執行緒環境中的,每次只會去處理一件事情。 這一種處理方式,避免了多執行緒環境的資料同步的問題,大大的提升了處理效率。 所謂事件迴圈,就是指處理器在一個程式週期中,處理完這個週期的事件之後,會進入下一個事件週期,處理下一個事件週期的事情,這樣一個週期一個週期的迴圈。 ## 事件迴圈的阻塞 如果我們在事件處理過程中,某個事件的處理髮生了阻塞,則會影響其他的事件的執行,所以我們可以看到在JS中,幾乎所有的IO都是非阻塞的。這也是為什麼javascript中有這麼多回調的原因。 ## 事件迴圈舉例 我們看一個簡單的事件迴圈的例子: ~~~js const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') action2() action3() } action1() ~~~ 上面的程式碼輸出: ~~~js action1 action2 action3 ~~~ ## 棧和訊息佇列 我們知道函式間的呼叫是通過棧來實現的,上面的例子中,我們的呼叫順序也是通過棧來實現的。 但並不是函式中所有的方法都會入棧,還有一些方法會被放入訊息佇列。 我們再舉一個例子: ~~~js const action2 = () => console.log('action2') const action3 = () => console.log('action3') const action1 = () => { console.log('action1') setTimeout(action2, 0) action3() } action1() ~~~ 上面的程式碼執行結果: ~~~js action1 action3 action2 ~~~ 結果不一樣了。這是因為settimeout觸發了定時器,當定時器到期的時候,回撥函式會被放入訊息佇列中等待被處理,而不是放入棧中。 事件迴圈會優先處理棧中的事件,只有棧中沒有任何資料的時候,才會去轉而消費訊息佇列中的事件。 雖然上面例子中setTimeout的timeout時間是0,但是還是要等到action3執行完畢才能執行。 > 注意,setTimeout中的timeout並不是在當前執行緒進行等待的,它是由瀏覽器或者其他JS執行環境來呼叫的。 ## 作業佇列和promise ES6中的Promise引入了作業佇列的概念,使用作業佇列將會盡快地執行非同步函式的結果,而不是放在呼叫堆疊的末尾。 舉個例子: ~~~js 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() ~~~ 輸出結果: ~~~js action1 action3 應該在action3之後、action2之前 action2 ~~~ 這是因為,在當前函式結束之前 resolve 的 Promise 會在當前函式之後被立即執行。 也就是說先執行棧,再執行作業佇列,最後執行訊息佇列。 ## process.nextTick() 先給大家一個定義叫做tick,一個tick就是指一個事件週期。而process.nextTick()就是指在下一個事件迴圈tick開始之前,呼叫這個函式: ~~~js process.nextTick(() => { console.log('i am the next tick'); }) ~~~ 所以nextTick一定要比訊息佇列的setTimeout要快。 ## setImmediate() nodejs提供了一個setImmediate方法,來儘快的執行程式碼。 ~~~js setImmediate(() => { console.log('I am immediate!'); }) ~~~ setImmediate中的函式會在事件迴圈的下一個迭代中執行。 setImmediate() 和 setTimeout(() => {}, 0)的功能基本上是類似的。它們都會在事件迴圈的下一個迭代中執行。 ## setInterval() 如果想要定時執行某些回撥函式,則需要用到setInterval。 ~~~js setInterval(() => { console.log('每隔2秒執行一次'); }, 2000) ~~~ 要清除上面的定時任務,可以使用clearInterval: ~~~js const id = setInterval(() => { console.log('每隔2秒執行一次'); }, 2000) clearInterval(id) ~~~ 注意,setInterval是每隔n毫秒啟動一個函式,不管該函式是否執行完畢。 如果一個函式執行時間太長,就會導致下一個函式同時執行的情況,怎麼解決這個問題呢? 我們可以考慮在回撥函式內部再次呼叫setTimeout,這樣形成遞迴的setTimeout呼叫: ~~~js const myFunction = () => { console.log('做完後,隔2s再次執行!'); setTimeout(myFunction, 2000) } setTimeout(myFunction, 2000) ~~~ > 本文作者:flydean程式那些事 > > 本文連結:[http://www.flydean.com/nodejs-event/](http://www.flydean.com/nodejs-event/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來