在使用 MongoDB 資料庫的時候,我們常常需要同時送出多筆查詢,並在所有資料都讀取完畢後進行下一步動作,例如將資料顯示在網頁上。這篇文章將會簡單介紹利用
Async.js 與
mongoose 達成上述需求的方法與程式碼。
![](https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_uSvS1pJScmGXKFzvkISY1Yr64emoa9CGAGur5rP0ifWAdE2YIL-qo_ot3Wtk2WA2eaI6DCSPxBKlnAvbpT52ye=s0-d) |
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 查詢的方法。