Node.js 異步模式淺析

注:此文是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 );
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

咱們本來覺得輸出的應該是: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     };
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方法,該方法的做用就是告訴系統,我不要執行控制權,你在你空閒的時候執行我給你的函數就好了.

 

好,異步中的一些陷井和基本已經介紹完了,歡迎你們補充。

相關文章
相關標籤/搜索