注:此文是node.js實戰讀後的總結。php
在日常的腳本語言中都是同步進行的,好比php,服務器處理多個請求的方法就是並行這些腳本。多任務處理,多線程等等。可是這種處理方式也有一個問題:每個進程或者線程都會耗費大量的系統資源。若是有一種方法能夠最大化的利用CPU的計算能力和可用內存以減小資源浪費那就極好了。這樣,咱們的node.js就應運而生了。html
上一個node.js最簡單的異步編程案例:node
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 );
從這個例子咱們就能夠看到在異步函數中使用的最多的回調函數,這些回調函數至少包含一個參數,即最後操做的狀態(成功仍是失敗),通常而言還有第二個參數,即最後操做返回的結果或信息(好比文件句柄,數據庫鏈接,查詢到的數據等),一些回調函數可能還包含更多的參數.假設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 );
可是,在異步處理的過程當中得注意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 });
咱們本來覺得輸出的應該是:api
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 };
好了,這樣咱們的輸出就和咱們想象中的同樣了,咱們在寫代碼時必定不要忘了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 }
當我數組中的元素特別大時,該函數就會耗費大量的計算時間。咱們知道在單線程模式中,node.js在同一時間只能作一件事,因此這耗費的大量時間將會成爲一個問題。而好比其餘一些計算哈希。摘要(digest)或者其餘一下耗費時間的操做就可能致使應用處於假死的狀態。因此說node.js並不適合計算服務器,nodejs更適合常見的網絡任務,好比那些須要大量I、O或者須要向其餘服務請求的任務,若是須要一個大量計算的服務器,第一個解決方法就是把這些操做遷移到其餘服務器上去,而後用nodejs遠程調用。但若是隻是偶爾執行這種任務,那麼還有第二種解決方法,那就是利用全局對象process的nextTick方法,該方法的做用就是告訴系統,我不要執行控制權,你在你空閒的時候執行我給你的函數就好了.
好,異步中的一些陷井和基本已經介紹完了,歡迎你們補充。