我正忙着为报告系统工作。异步节点给了我一些问题,尽管我并不强迫它同步。
我们正在使用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;
});
这里的问题是逻辑/异步。不是语法,因此是半伪代码。
非常感谢任何帮助或建议。
答案 0 :(得分:2)
您需要熟悉承诺,并且需要熟悉异步。 因为你要返回一个数组,这就是你要获得的价值。
在处理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
// Promises
getSetOfIDs()
.then(ids => Promise.all(ids.map(getThingByID)))
.then(things => app.display(things))
.catch(err => app.apologize(err));
要真正了解这里发生的事情,请学习Promise,Promise.all静态方法和array.map()
。
这两组代码在理论上完全相同,只是在最后一种情况getSetOfIDs
和getThingByID
不进行回调,它们会返回承诺。
答案 1 :(得分:0)
通常在异步调用中,在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;
});
答案 2 :(得分:0)
只需使用承诺:
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)
})