ajax的盛行,使javascript成爲前端開發人員的寵兒.現今js已經可以通過nodejs在服務器運行.本文將通過對一段代碼的逐漸改進來展示如何讓程序員使用javascript快樂得進行非阻塞異步編程.nodejs和mongodb的具體安裝和使用方法請參考文章最後的參考文獻列表.
Hello,Node !
nodejs是編程就緒的,任何功能都要通過編程實現.這和Tomcat,ApacheHTTP等運行就緒服務不同,後者安裝後可以直接運行就可以通過瀏覽器訪問.而nodejs起碼要有如下代碼.
var http = require('http'); http.createServer(function(req,resp){ resp.end('Hello , Node !'); }).listen(8124); console.log('server startup');
將這些代碼保存爲hellonode.js然後在命令行執行node hellonode.js.在瀏覽器通過http://localhost:8124/即可訪問該服務,瀏覽器窗口會顯示"Hello , Node !"字樣.這段代碼的意義很容易理解,開頭的require相當於java中的import將http模塊引入,後面使用http模塊的API構建http服務.javascript回調用來處理收到的請求,並返回字符串給瀏覽器.
一個稍微複雜的例子
現實環境中,常規得處理http請求的過程大概是這樣的.首先,服務器收到請求並從中解析出請求參數,然後使用這些參數進行數據庫查詢,最後將得到的結果放入模板生成視圖響應給用戶.僞代碼大概如下這個樣子.
var http = require('http');
var queryString = require('queryString');
var url = require('url');
var db = {};//初始化數據庫
http.createServer(function (req, resp) {
//1.解析請求參數
var params = queryString.parse(url.parse(req.url).query);
var template = 'result = _';
//2.使用參數查詢數據
db.query(params, function (err, result) {
//3.在回調中使用查詢結果
if (err) throw err;
var view = template.replace(/_/, result);
//4.返回視圖,響應給客戶端
resp.end(view);
});
}).listen(8124);
console.log('server startup');
變得更復雜
可以看到代碼(2)部分由於數據庫查詢在node中是無阻塞的,所以必須將所有後續處理查詢結果的代碼作爲回調來使用,上面代碼的處理過程比較簡單,但是如果放入真實的應用中,不一定如此,比如模板要根據請求參數從本地文件系統加載.代碼就會變爲如下樣子.
var http = require('http'); var queryString = require('queryString'); var url = require('url'); var fs = require('fs'); var db = {};//初始化數據庫 http.createServer(function (req, resp) { //解析請求參數 var params = queryString.parse(url.parse(req.url).query); //使用參數查詢數據 db.query(params, function (err, result) { //在回調中使用查詢結果 if (err) throw err; fs.readFile(params.path, function (err, template) { if (err) throw err; //填充模板 var view = template.replace(/_/, result); //返回 resp.end(view); }) }); resp.end('Hello , Node !'); }).listen(8124); console.log('server startup');
深層嵌套問題
可以看到由於異步編程所以難免產生了又一層的嵌套回調.可以想想當處理過程更復雜的時候,會產生像如下的深層嵌套問題.
setp1(function (err, result1) { setp2(result1, function (err, result2) { setp3(result2, function (err, result3) { step4(result3, function (err, result4) { //更多的嵌套回調 }) }) }) });
事件模型和Promise
這樣代碼的編寫閱讀都非常困難.好在現在有許多編程模型可以解決這個問題,例如事件模型(發佈訂閱).
//註冊事件監聽器 server.on('connection', function (stream) { console.log('someone connected!'); }); //當如下代碼被調用時上面的註冊回調將被調用 server.emit('connection')//發佈事件
不過這樣有一個問題,可以想象如果將上面的深層嵌套用這種方式編寫,需要發佈四次事件和註冊四次監聽器.本來一段內聚的代碼會變得相當鬆散,依然沒有根本解決讀寫理解困難的問題.下面介紹Promise模型.大概可以這樣理解,每當異步處理執行時,總是返回一個Promise,該Promise保證在處理完成或發成錯誤時才調用後續的方法.Promise通常有這樣的API.
promise.then(success,failure)
success,failure分別表示promise承諾的處理成功或失敗時的回調函數.
上面的深層嵌套用該模型重寫即可變爲如下形式
step1().then(step2).then(step3).then(step3)
這樣就容易理解多了.要使用Promise模型,需要想node中引入支持庫,我們使用'q'這個模塊.可以使用npm install q進行安裝.'q'這個模塊提供了很多工具方法來將普通的回調轉換爲Promise.使用'q'改寫最上面的請求處理,僞代碼如下:
var http = require('http'); var queryString = require('queryString'); var url = require('url'); var fs = require('fs'); var q = require('q'); var db = {};//初始化數據庫 http.createServer(function (req, resp) { //解析請求參數 var params = queryString.parse(url.parse(req.url).query); //q.all將數組中的promise合併 q.all([ //q.ninvoke將原本的回調式調用轉化爲返回Promise對象 q.ninvoke(db, 'query', params), q.ninvoke(fs, 'readFile', params.path) ]) //當上述兩個promise都處理完成時繼續調用 .then(function (result) { //返回結果爲一個數組,數組中分別存放上面被組合的promise的結果 var model = result[0]; var template = result[1]; var view = template.replace(/_/, model); resp.end(view); }); }).listen(8124); console.log('server startup');
這樣看起來就容易理解多了,但問題是內部細節很難懂.好在有'q'這樣的庫幫幫我們處理.
使用CoffeeScript讓代碼變得更緊湊
javascript是一門古老的語言,不免語法冗餘了點兒,好在現在有coffeescript這類的簡單語言可以編譯爲javascript來運行.使用coffee來改寫上面代碼,就更加緊湊了.
http.createServer (req, resp)-> param = queryString.parse(url.parse(req.url).query) q.all([ q.ninvoke(db, 'query', params), q.ninvoke(fs, 'readFile', params.path)]) .then((result)-> model = result[0] template = result[1] view = template.replace(/_/, modle) resp.end view) console.log 'server startup'
參考文獻