Copyright jaketrent.com |
Async.js 簡介
在 javascript 中雖然有著 callback 機制,可以在事件完成後接著執行其他動作。但如果要等待很多事件完成,可能會寫成很多層的 callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var findData = function(input, callback){ var data = new Data; findMoreData(inputA, function(dataA){ data.add(dataA); findMoreData(inputB, function(dataB){ data.add(dataB); findMoreData(inputC, function(dataC){ ... }); }); }); callback(data); } |
這種寫法不僅難看,而且由於並非平行處理,在每個 findMoreData 函式中等待 I/O 處理的時間都會累積起來,影響效能。
我們可以使用 Async.js 套件提供的 parallel 方法,來改寫上面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | var async = require('async'); var findData = function(input, callback){ async.parallel({ A: function(cb){ findMoreData(inputA, function(dataA){ cb(null, dataA); }); }, B: function(cb){ findMoreData(inputB, function(dataB){ cb(null, dataB); }); }, ... }, // 這裡是 async.parallel 的 callback function function(err, results) { // 在這裡 results 的內容是個物件:{A: dataA, B: dataB, ...} callback(results); }); } |
上面的 async.parallel 方法接受一個物件當作參數,裡面包含各個需要平行處理的工作。範例中為了獲得資料 A,我們呼叫了 findMoreData 來處理,而在 findMoreData 等待 I/O 處理的時間中,parallel 會試著繼續處理資料 B 的部分。因此,我們可以平行化的處理這些工作,不用等待前一個完成才接著執行。
程式碼中的 cb 是 Async.js 的一個 callback function。執行後,parallel 就會認為這部分的工作已經完成,而我們要確保每個工作都會呼叫到 cb,整個 parallel 的處理流程才會正常結束。cb 接受兩個參數,第一個是 error 物件(可以為 null),第二個則是我們要的資料,它會存到 results 裡面。
等到獲得所有資料,最後一段的 callback function就會被執行,讓使用者可以處理後續工作。
parallel 與 Mongoose 的整合
對於 Async.js 有簡單的認識後,我們正式把 mongoose 讀取資料的 API 整合進來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | var async = require('async'); var mongoose = require('mongoose'); var Schema = mongoose.Schema; var dataASchema = new Schema({...}); var dataAModel = mongoose.model('dataA', dataASchema); ... var findData = function(callback){ async.parallel({ A: function(cb){ dataAModel.find({}, function(err, data, count){ cb(null, data); }); }, B: function(cb){ dataBModel.find({}, function(err, data, count){ cb(null, data); }); }, ... }, function(err, results) { callback(results); }); } |
這邊用 find 函式來查詢資料庫的資料,並把得到的全部結果原封不動的存放到 results 去。藉由修改 find 的參數,我們也可以在這邊針對不同資料,自訂各項查詢條件。
改用 each 取得資料
使用 parallel 可以自訂要同步執行的不同工作,因此有著很大的彈性。但有時候我們所需要的只是針對不同資料執行相同的工作,用 parallel 必須要不斷的重複相同的程式碼(如上例中,不斷重複 find 的動作),看起來有些雜亂。
Async.js 另外提供其他流程控制與資料處理方法,其中一個方法是 each,它可以針對多筆資料平行處理相同的一件工作。例如以下範例,使用 each 來取得資料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var findData = function(array, callback){ var results = {}; async.each(array, function(item, cb){ var model = mongoose.model(item); model.find({},function(err, data, count){ results[item] = data; cb(); }); }, function(err){ callback(results); }); } findData(['dataA', 'dataB'], function(results){ // results 物件的內容為 {'dataA': ... , 'dataB': ...} // 省略部分為資料庫中的資料 }); |
我們輸入一個參數 array,裡面存放不同 MongoDB collection 的名字,async.each 會去讀取這些 collection 名字,再利用 find 找到當中的所有資料,存放到物件變數 results 當中,最後呼叫 cb 結束。
結論
這篇文章簡單介紹 Async.js 裡的 parallel 與 each 兩種方法,並配合 mongoose 來達到平行處理多筆 MongoDB 查詢的方法。
沒有留言:
張貼留言