nodejs的精髓就是"異步",但什么是異步呢?我們來看一個例子:
1 var start =new Date; 2 setTimeout(function(){ 3 var end =new Date; 4 console.log('Time elapsed:', end - start, 'ms'); 5 }, 500); 6 while (new Date - start < 1000) { 7 console.log("hello world!<br/>"); 8 };
按照"Java"編程的思維習慣,應該是行1處定義了一個Date類型的變量,然后500毫秒后在顯示 "Time elapsed:500 ms",再然后不斷的輸出"hello world!",持續大約500毫秒吧。
事實上不是這個樣子的,當執行到行2時,發現了一個延時函數setTimeout,這時候呢,node不會停止執行,而是把這個函數放到了一個事件列表中,繼續執行以后的代碼,一直等到了所有的東西都處理完了,然后JavaScript虛擬機才會問 "隊列里還有誰啊",然后再順序的處理事件,這就是JS的一個很重的的特性。參考《JavaScript異步編程》。
知道了是一回事,但真正理解了又是另一回事。
對數據庫的增刪改查是最經常的操作了,下面咱就來開發一個往mysql的一個表person插入一條數據的小功能。代碼如下:
1 var db = require('./db'); 2 3 db.connectionReady(db.server); 4 5 function user(name,birthday,password){ 6 this.name = name; 7 this.birthday = birthday; 8 this.password = password; 9 } 10 11 module.exports = user; 12 user.prototype.save = function(){ 13 var id = getId(); 14 db.server.query('INSERT INTO person SET id=?,name=?,birthday=?,password=?', 15 [id,this.name,this.birthday,this.password], 16 function(error,results){ 17 if(error){ 18 console.log('ClientReady Error:'+error.message); 19 db.server.end(); 20 return; 21 } 22 }); 23 } 24 25 getId=function(){ 26 var id; 27 var n = this.name; 28 var b = this.birthday; 29 var p = this.password; 30 db.server.query('select count(*) from person', 31 function(error,results,fields){ 32 if(error){ 33 console.log('ClientReady Error:'+error.message); 34 db.server.end(); 35 return; 36 } 37 id = parseInt(results[0]['count(*)'])+1; 38 console.log("id=: "+id); 39 }); 40 return id; 41 }
代碼很簡單,在user.prototype.save函數里呢,我們調用了getId(),以得到最大的id+1來當作新id,然后保存到數據庫里。好像很簡單,沒什么問題,那我們來做個測試吧:
1 var User = require('./user'); 2 3 var user = new User('zhangsan','1986-04-23','123'); 4 user.save();
運行一下看看:

在user代碼的38行處顯示了id為4,但怎么又報了 id cannot be null呢?
還是異步特性在作怪!!
我們來看看user.js的代碼:
在13行處,我們獲得了id號,然后把它放到新的行里的id里面。問題是"這個id真的存在了嗎?"
在30行處的db.server.query里有個回調函數,這個回調函數是什么呢,它是執行完了這個查詢后的返回結果!!!在沒有執行完的時候呢,js虛擬機會繼續運行程序,先運行40行,把未賦值的id返回了,繼續往先走,走到14行,吧這個賦值的id當作了要插入行的主鍵,所以就會顯示了報的錯誤。其實歸根結底還是我們的同步思維。在getId函數里,我們想當然的認為id在37行處一定會被賦值!其實根據一開始講到的,它肯定不會被賦值,因為在這個回調函數執行之前,先要去查詢db,然后cpu是不會等待結果返回的,它一定會繼續向下執行!
那我們該怎么辦呢?難道對於這種事件依賴類型的需求,nodejs無法滿足我們嗎?要是連這點需求都做不到,估計nodejs還沒被我們所知道之前就"夭折"了。
既然我們無法在拿到id之后執行下一步的操作,那就拿到它之后再操作不就行了!沒錯,就是把插入操作放到getId函數的回調函數里面去。如下所示:
1 getId=function(){ 2 var n = this.name; 3 var b = this.birthday; 4 var p = this.password; 5 db.server.query('select count(*) from person', 6 function(error,results,fields){ 7 if(error){ 8 console.log('ClientReady Error:'+error.message); 9 db.server.end(); 10 return; 11 } 12 var id = parseInt(results[0]['count(*)'])+1; 13 db.server.query('INSERT INTO person SET id=?,name=?,birthday=?,password=?', 14 [id,this.name,this.birthday,this.password], 15 function(error,results){ 16 if(error){ 17 console.log('ClientReady Error:'+error.message); 18 db.server.end(); 19 return; 20 } 21 }); 22 }); 23 }
這樣把save操作放到了getId()方法的回調函數里面來完成了,所以nodejs的思路就從
得到A的結果進行B運算變成了進行A,並在A的回調函數里面進行B運算。要是還有依賴B節點的操作呢?沒錯,就是在B的回調函數里寫操作唄。難改網上很多人抱怨nodejs回調函數太多,不好閱讀了!
那有沒有解決這個問題的辦法呢,且待下次分解吧。