node.js異步編程

        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'

 

 

參考文獻

Node.js究竟是什麼?

Node.js API

node/q 項目

CoffeeScript

javascript異步編程的4種方法

JavaScript異步編程的Promise模式

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章