有很多人對Node.js裏process.nextTick()的用法感到不理解,下面我們就來看一下process.nextTick()到底是什麼,該如何使用。
Node.js是單線程的,除了系統IO之外,在它的事件輪詢過程中,同一時間只會處理一個事件。你可以把事件輪詢想象成一個大的隊列,在每個時間點上,系統只會處理一個事件。即使你的電腦有多個CPU核心,你也無法同時並行的處理多個事件。但也就是這種特性使得node.js適合處理I/O型的應用,不適合那種CPU運算型的應用。在每個I/O型的應用中,你只需要給每一個輸入輸出定義一個回調函數即可,他們會自動加入到事件輪詢的處理隊列裏。當I/O操作完成後,這個回調函數會被觸發。然後系統會繼續處理其他的請求。
在這種處理模式下,process.nextTick()的意思就是定義出一個動作,並且讓這個動作在下一個事件輪詢的時間點上執行。我們來看一個例子。例子中有一個foo(),你想在下一個時間點上調用他,可以這麼做:
運行上面的代碼,你從下面終端打印的信息會看到,"bar"的輸出在“foo”的前面。這就驗證了上面的說法,foo()是在下一個時間點運行的。
你也可以使用setTimeout()函數來達到貌似同樣的執行效果:
但在內部的處理機制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一個單純的延時,他有更多的 特性。
更精確的說,process.nextTick()定義的調用會創建一個新的子堆棧。在當前的棧裏,你可以執行任意多的操作。但一旦調用netxTick,函數就必須返回到父堆棧。然後事件輪詢機制又重新等待處理新的事件,如果發現nextTick的調用,就會創建一個新的棧。
下面我們來看看,什麼情況下使用process.nextTick():
在多個事件裏交叉執行CPU運算密集型的任務:
在下面的例子裏有一個compute(),我們希望這個函數儘可能持續的執行,來進行一些運算密集的任務。
但與此同時,我們還希望系統不要被這個函數堵塞住,還需要能響應處理別的事件。這個應用模式就像一個單線程的web服務server。在這裏我們就可以使用process.nextTick()來交叉執行compute()和正常的事件響應。
01 |
var http
= require( 'http' ); |
06 |
process.nextTick(compute); |
09 |
http.createServer( function (req,
res) { |
10 |
res.writeHead(200,
{ 'Content-Type' : 'text/plain' }); |
11 |
res.end( 'Hello
World' ); |
12 |
}).listen(5000, '127.0.0.1' ); |
在這種模式下,我們不需要遞歸的調用compute(),我們只需要在事件循環中使用process.nextTick()定義compute()在下一個時間點執行即可。在這個過程中,如果有新的http請求進來,事件循環機制會先處理新的請求,然後再調用compute()。反之,如果你把compute()放在一個遞歸調用裏,那系統就會一直阻塞在compute()裏,無法處理新的http請求了。你可以自己試試。
當然,我們無法通過process.nextTick()來獲得多CPU下並行執行的真正好處,這只是模擬同一個應用在CPU上分段執行而已。
保持回調函數異步執行的原則
當你給一個函數定義一個回調函數時,你要確保這個回調是被異步執行的。下面我們看一個例子,例子中的回調違反了這一原則:
1 |
function asyncFake(data,
callback) { |
2 |
if (data
=== 'foo' )
callback( true ); |
6 |
asyncFake( 'bar' , function (result)
{ |
爲什麼這樣不好呢?我們來看Node.js 文檔裏一段代碼:
1 |
var client
= net.connect(8124, function ()
{ |
2 |
console.log( 'client
connected' ); |
3 |
client.write( 'world!\r\n' ); |
在上面的代碼裏,如果因爲某種原因,net.connect()變成同步執行的了,回調函數就會被立刻執行,因此回調函數寫到客戶端的變量就永遠不會被初始化了。
這種情況下我們就可以使用process.nextTick()把上面asyncFake()改成異步執行的:
1 |
function asyncReal(data,
callback) { |
2 |
process.nextTick( function ()
{ |
3 |
callback(data
=== 'foo' ); |
用在事件觸發過程中
來看一個例子,你想寫一個庫實現這樣的功能:從源文件裏讀取數據,當讀取完畢後,觸發一個事件同時傳遞讀取的數據。可能你會這樣寫:
1 |
var EventEmitter
= require( 'events' ).EventEmitter; |
3 |
function StreamLibrary(resourceName)
{ |
7 |
this .emit( 'data' ,
chunkRead); |
9 |
StreamLibrary.prototype.__proto__
= EventEmitter.prototype; |
下面是一段調用這個庫的客戶端程序,我們想在程序中監聽這些事件:
1 |
var stream
= new StreamLibrary( 'fooResource' ); |
3 |
stream.on( 'start' , function ()
{ |
4 |
console.log( 'Reading
has started' ); |
7 |
stream.on( 'data' , function (chunk)
{ |
8 |
console.log( 'Received:
' +
chunk); |
但是上面的代碼中,將永遠接收不到“start”事件,因爲在這個庫實例化的時候,“start”事件會被立刻觸發執行,但此時事件的回調函數還沒有準備好,所以在客戶端根本無法接收到這個事件。同樣,我們可以用process.nextTick()來改寫事件觸發的過程,下面是一個正確的版本:
01 |
function StreamLibrary(resourceName)
{ |
04 |
process.nextTick( function ()
{ |
09 |
this .emit( 'data' ,
chunkRead); |
本文轉自:http://www.oschina.net/translate/understanding-process-next-tick?cmp