標籤:style blog http color io os 使用 ar for
注:此文是node.js實戰讀後的總結。
在平常的指令碼語言中都是同步進行的,比如php,伺服器處理多個請求的方法就是並行這些指令碼。多任務處理,多線程等等。但是這種處理方式也有一個問題:每一個進程或者線程都會耗費大量的系統資源。如果有一種方法可以最大化的利用CPU的計算能力和可用記憶體以減少資源浪費那就極好了。這樣,我們的node.js就應運而生了。
上一個node.js最簡單的非同步編程案例:
1 var fs = require(‘fs‘); 2 3 var file; 4 5 fs.open( 6 ‘info.txt‘,‘r‘, 7 function(err,handle){ 8 var buf = new Buffer(100000); 9 fs.read(10 handle,buf,0,100000,null,11 function(err,length){12 console.log(buf.toString(‘utf8‘,0,length));13 fs.close(handle,function(){});14 });15 16 }17 );View Code
從這個例子我們就可以看到在非同步函數中使用的最多的回呼函數,這些回呼函數至少包含一個參數,即最後操作的狀態(成功還是失敗),一般而言還有第二個參數,即最後操作返回的結果或資訊(比如檔案控制代碼,資料庫連接,查詢到的資料等),一些回呼函數可能還包含更多的參數.假設err代表返回的狀態參數,則該參數的值一般會有以下幾種情況:1.null:表示操作成功,並且會有一個傳回值(如果你需要的話).2.一個error對象的執行個體:通常人們習慣在error對象上添加code欄位並且用message欄位來儲存錯誤資訊(注:這種方式可以讓我們寫出的非阻塞代碼更具可控性)。對上面的代碼進行最佳化加上錯誤處理:
1 var fs = require(‘fs‘); 2 3 var file; 4 5 fs.open( 6 ‘info.txt‘,‘r‘, 7 function(err,handle){ 8 //第一種錯誤處理方式 9 if(err)10 {11 console.log("ERROR:"+ err.code + "("+err.message+")");12 return;13 }14 var buf = new Buffer(100000);15 fs.read(16 handle,buf,0,100000,null,17 function(err,length){18 //第二種錯誤處理方式19 if(err){20 console.log("ERROR:"+err.code+"("+err.message+")");21 }else{22 console.log(buf.toString(‘utf8‘,0,length));23 fs.close(handle,function(){});24 }25 26 });27 28 }29 );View Code
但是,在非同步處理的過程中得注意this的用法以及函數範圍的變化,看下面的代碼:
1 var fs = require(‘fs‘); 2 3 function FileObject(){ 4 this.filename = ‘‘; 5 6 this.file_exists = function(callback){ 7 console.log("About to open:"+this.filename); 8 fs.open(this.filename,‘r‘,function(err,handle){ 9 if(err){10 console.log("Can‘t open:"+ this.filename);11 callback(err);12 return;13 }14 fs.close(handle,function(){});15 callback(null,true);16 });17 };18 }19 20 var fo = new FileObject();21 fo.filename = ‘info‘;22 fo.file_exists(function(err,results){23 if(err){24 console.log("ERROR:"+err.code+"("+err.message+")");25 return;26 }27 console.log("file exists!!!");28 });View Code
我們原本以為輸出的應該是:
About to open:info
Can‘t open:info
但是實際上確實:
About to open:info
Can‘t open:undefined
ERROR:ENOENT(ENOENT, open ‘G:\nodejs\info‘)
這是為什麼呢?在我們的理解中,大多數情況下,當一個函數嵌套在另一個函數中時,他就會自動繼承父函數的範圍,因而就能訪問所有的變數了。但是為什麼我們嵌套的回呼函數卻沒有出現我們以為的輸出呢?
這個問題得歸結於this關鍵字和非同步回呼函數本身。在我們調用fs.open函數的時候,他會先初始化自己,然後調用底層的作業系統函數(在我們的代碼中,就是開啟檔案),並且把回呼函數插入到node.js的事件隊列中去,執行完會立即返回給file_exists函數,然後退出。當fs.open完成任務後,node就會調用該回呼函數,但此時,該函數已經不再擁有FileObject這個類的繼承關係了,所以回呼函數會重新賦予新的this指標,在這一過程中我們就丟失了我們的FileObject的this指標,故我們就不能訪問我們的file_name。但是在這個過程中,回呼函數的範圍還保留著。這裡關係到nodejs的事件模式(參考資料:http://nodejs.org/docs/latest/api/events.html),這個也是nodejs的一個重要特性,我們現在不多說。這種錯誤最常見的解決方案就是把消失的this指標儲存在變數中,下面我們來重寫
1 this.file_exists = function(callback){ 2 //用一個變數來儲存this指標 3 var self = this; 4 console.log("About to open:"+self.filename); 5 fs.open(this.filename,‘r‘,function(err,handle){ 6 if(err){ 7 console.log("Can‘t open:"+ self.filename); 8 callback(err); 9 return;10 }11 fs.close(handle,function(){});12 callback(null,true);13 });14 };View Code
好了,這樣我們的輸出就和我們想象中的一樣了,我們在寫代碼時一定不要忘了this的變化,不然就可能出現很多bug = = .
好,我們接著說.我們都知道Node運行在單線程中,使用事件輪詢來調用外部函數和服務。它將回呼函數插入事件隊列中來等待響應,並且儘快執行回呼函數。好,下面我們來看一個函數,這個函數的功能就是計算兩個數組的交叉元素:
1 function compute_intersection(arr1,arr2,callback){ 2 var results = []; 3 for(var i = 0; i<arr1.length; i++) 4 for(var j = 0; j<arr2.length; j++){ 5 if(arr1[i] == arr2[j]){ 6 results.push(arr1[i]); 7 bareak; 8 } 9 }10 callback(null,true);11 }View Code
當我數組中的元素特別大時,該函數就會耗費大量的計算時間。我們知道在單線程模式中,node.js在同一時間只能做一件事,所以這耗費的大量時間將會成為一個問題。而比如其他一些計算雜湊。摘要(digest)或者其他一下耗費時間的操作就可能導致應用處於假死的狀態。所以說node.js並不適合計算服務器,nodejs更適合常見的網路任務,比如那些需要大量I、O或者需要向其他服務要求的任務,如果需要一個大量計算的伺服器,第一個解決方案就是把這些操作遷移到其他伺服器上去,然後用nodejs遠程調用。但如果只是偶爾執行這種任務,那麼還有第二種解決方案,那就是利用全域對象process的nextTick方法,該方法的作用就是告訴系統,我不要執行控制權,你在你閒置時候執行我給你的函數就行了.
好,非同步中的一些陷井和基本已經介紹完了,歡迎大家補充。
Node.js 非同步模式淺析