[英]Node.js looping async calls
我正在為報告系統的端點工作。 節點異步給了我一些問題,盡管我不想強迫它同步。
我們正在使用MongoDB和Mongoose。 我必須查詢集合A的正則表達式,然后對於每個返回的文檔,查詢多個包含的文檔以填充要返回的JSON對象/數組。
除了最終循環查詢(異步啟動並提前返回報告的地方)外,我可以對大多數數據使用populate
。 有沒有一種優雅的方法可以做到這一點? 還是我應該拆分為一個不同的函數,並多次調用該函數來堅持做這些functions should do only one thing
?
示例代碼:
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) {
var report = [];
A.map(function(a)){
report[a.name] = [];
D.aggregate([
{
$match: {
id: B._id
}
},
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
], function(err, result) {
C.map(function(c){
report[a.name].push({
'field1': c.field1,
'field2': c.field2,
'field3': c.field3,
'count': result.count
});
});
});
}
return report;
});
這里的問題是邏輯/異步。 不使用語法,因此不使用半偽代碼。
任何幫助或建議,將不勝感激。
您需要使自己熟悉Promise和異步。 因為您要返回一個數組,所以這就是您要獲得的值。
在處理Async時,您有幾種選擇,但是在您的情況下,您想看看兩種解決方案:
// callbacks
getSetOfIDs((err, ids) => {
let remaining = ids.length;
let things = [];
let failed = false;
ids.forEach(id => {
getThingByID(id, (err, thing) => {
if (failed) { return; }
if (err) {
failed = true;
handleFailure(err);
} else {
remaining -= 1;
things.push(thing);
if (!remaining) {
handleSuccess(things);
}
}
});
});
});
注意,我沒有返回任何things
,而是將其傳遞給了回調。
您可以使用高階函數來清理此類事件。
// cleaned up callbacks
function handleNodeCallback (succeed, fail) {
return function (err, data) {
if (err) {
fail(err);
} else {
succeed(data);
}
};
}
function handleAggregateCallback (succeed, fail, count) {
let items = [];
let failed = false;
const ifNotFailed = cb => data => {
if (!failed) { cb(data); }
};
const handleSuccess = ifNotFailed((item) => {
items.push(item);
if (items.length === count) { succeed(items); }
});
const handleFailure = ifNotFailed((err) => {
failed = true;
fail(err);
});
return handleNodeCallback(handleSuccess, handleFailure);
}
稍后提供一些輔助代碼,我們可以開始了:
// refactored callback app code (note that it's much less scary)
getSetOfIDs((err, ids) => {
const succeed = (things) => app.display(things);
const fail = err => app.apologize(err);
if (err) { return fail(err); }
let onThingResponse = handleAggregateCallback(succeed, fail, ids.length);
ids.forEach(id => getThingByID(id, onThingResponse));
});
請注意,除了高階函數外,我從不返回任何東西,而是始終傳遞延續(接下來要做的事情,帶有一個值)。
另一種方法是承諾
// Promises
getSetOfIDs()
.then(ids => Promise.all(ids.map(getThingByID)))
.then(things => app.display(things))
.catch(err => app.apologize(err));
要真正了解這里發生的事情,請學習Promises,Promise.all靜態方法和array.map()
。
從理論上講,這兩組代碼都做同樣的事情,除了在最后一種情況下, getSetOfIDs
和getThingByID
不接受回調,它們返回的是getThingByID
。
通常在異步調用中,在return語句之后,任何操作都將被取消。
也許只有在一切都做好之后,您才能返回報告對象。
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) {
var report = [];
A.map(function(a)){
report[a.name] = D.aggregate([
{
$match: {
id: B._id
}
},
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
], function(err, result) {
if(err){
return [];
}
var fields = []
C.map(function(c){
fields.push({
'field1': c.field1,
'field2': c.field2,
'field3': c.field3,
'count': result.count
});
});
return fields;
});
}
return report;
});
只需使用諾言:
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) {
var report = [];
return Promise.all([
A.map(function(a)){
return new Promise(function(resolve, reject) {
report[a.name] = [];
D.aggregate([{ $match: { id: B._id }},{$group: {_id: null,count: { $sum: 1 }}}],
function(err, result) {
if(err) {
reject(err)
} else {
C.map(function(c){
report[a.name].push({
'field1': c.field1,
'field2': c.field2,
'field3': c.field3,
'count': result.count
});
});
resolve(report)
}
});
}
})])
})
.then(function(report){
console.log(report)
})
.catch(function(err){
console.log(err)
})
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.