要介紹callback之前,要先提到Javascript的特色。Javascript是一種函數式語言(functional language),所有Javascript語言內的函數,都是高階函數(higher order function,這是數學名詞,計算機用語好像是first class function,意指函數使用沒有任何限制,與其他物件一樣)。也就是說,函數可以作為函數的參數傳給函數,也可以當作函數的返回值。這個特性,讓Javascript的函數,使用上非常有彈性,而且功能強大。 callback在形式上,其實就是把函數傳給函數,然後在適當的時機呼叫傳入的函數。Javascript使用的事件系統,通常就是使用這種形式。NodeJS中,有一個物件叫做EventEmitter,這是NodeJS事件處理的核心物件,所有會使用事件處理的函數,都會「繼承」這個物件。(這裡說的繼承,實作上應該像是mixin)他的使用很簡單:
這是Observer Pattern的簡單實作,而且跟在網頁中使用DOM的addEventListener使用上很類似,也很容易上手。不過NodeJS是大量使用非同步方式執行的應用,所以程式邏輯幾乎都是寫在callback函數中,當邏輯比較複雜時,大量的callback會讓程式看起來很複雜,也比較難單元測試。舉例來說: var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory}); p_client.open(function(err, p_client) { p_client.dropDatabase(function(err, done) { p_client.createCollection('test_custom_key', function(err, collection) { collection.insert({'a':1}, function(err, docs) { collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) { cursor.toArray(function(err, items) { test.assertEquals(1, items.length); p_client.close(); }); }); }); }); }); }); 這是在網路上看到的一段操作mongodb的程式碼,為了循序操作,所以必須在一個callback裡面呼叫下一個動作要使用的函數,這個函數裡面還是會使用callback,最後就形成一個非常深的巢狀。 這樣的程式碼,會比較難進行單元測試。有一個簡單的解決方式,是盡量不要使用匿名函數來當作callback或是event handler。透過這樣的方式,就可以對各個handler做單元測試了。例如: var http = require('http'); var tools = { cookieParser: function(request, response) { if(request.headers['Cookie']) { //do parsing } } }; var server = http.createServer(function(request, response) { this.emit('init', request, response); //... }); server.on('init', tools.cookieParser); server.listen(8080, '127.0.0.1'); 更進一步,可以把tools改成外部module,例如叫做tools.js: module.exports = { cookieParser: function(request, response) { if(request.headers['Cookie']) { //do parsing } } }; 然後把程式改成: var http = require('http'); var server = http.createServer(function(request, response) { this.emit('init', request, response); //... }); server.on('init', require('./tools').cookieParser); server.listen(8080, '127.0.0.1'); 這樣就可以單元測試cookieParser了。例如使用nodeunit時,可以這樣寫: var testCase = require('nodeunit').testCase; module.exports = testCase({ "setUp": function(cb) { this.request = { headers: { Cookie: 'name1:val1; name2:val2' } }; this.response = {}; this.result = {name1:'val1',name2:'val2'}; cb(); }, "tearDown": function(cb) { cb(); }, "normal_case": function(test) { test.expect(1); var obj = require('./tools').cookieParser(this.request, this.response); test.deepEqual(obj, this.result); test.done(); } }); 善於利用模組,可以讓程式更好維護與測試。 |
NodeJS 開發大全 > 2. Javascript 與nodeJS >