[英]in nodejs, how to stop a FOR loop until mongodb call returns
請查看下面的代碼段。 我有一個名為'stuObjList'的JSON對象數組。 我想循環通過數組來查找具有特定標志集的特定JSON對象,然后進行數據庫調用以檢索更多數據。
當然,FOR循環不等待db調用返回並以j == length到達結尾。 當db調用返回時,索引'j'超出了數組索引。 我理解node.js是如何工作的,這是預期的行為。
我的問題是,這里的工作是什么。 我怎樣才能實現我想要實現的目標?
...............
...............
...............
else
{
console.log("stuObjList.length: " + stuObjList.length);
var j = 0;
for(j = 0; j < stuObjList.length; j++)
{
if(stuObjList[j]['honor_student'] != null)
{
db.collection("students").findOne({'_id' : stuObjList[j]['_id'];}, function(err, origStuObj)
{
var marker = stuObjList[j]['_id'];
var major = stuObjList[j]['major'];
});
}
if(j == stuObjList.length)
{
process.nextTick(function()
{
callback(stuObjList);
});
}
}
}
});
“ async ”是一個非常流行的模塊,用於抽象異步循環並使代碼更易於讀取/維護。 例如:
var async = require('async');
function getHonorStudentsFrom(stuObjList, callback) {
var honorStudents = [];
// The 'async.forEach()' function will call 'iteratorFcn' for each element in
// stuObjList, passing a student object as the first param and a callback
// function as the second param. Run the callback to indicate that you're
// done working with the current student object. Anything you pass to done()
// is interpreted as an error. In that scenario, the iterating will stop and
// the error will be passed to the 'doneIteratingFcn' function defined below.
var iteratorFcn = function(stuObj, done) {
// If the current student object doesn't have the 'honor_student' property
// then move on to the next iteration.
if( !stuObj.honor_student ) {
done();
return; // The return statement ensures that no further code in this
// function is executed after the call to done(). This allows
// us to avoid writing an 'else' block.
}
db.collection("students").findOne({'_id' : stuObj._id}, function(err, honorStudent)
{
if(err) {
done(err);
return;
}
honorStudents.push(honorStudent);
done();
return;
});
};
var doneIteratingFcn = function(err) {
// In your 'callback' implementation, check to see if err is null/undefined
// to know if something went wrong.
callback(err, honorStudents);
};
// iteratorFcn will be called for each element in stuObjList.
async.forEach(stuObjList, iteratorFcn, doneIteratingFcn);
}
所以你可以像這樣使用它:
getHonorStudentsFrom(studentObjs, function(err, honorStudents) {
if(err) {
// Handle the error
return;
}
// Do something with honroStudents
});
注意.forEach()將為stuObjList中的每個元素“並行”調用你的迭代器函數(即,在下一個數組元素上調用它之前,它不會等待一個迭代器函數完成對一個數組元素的調用)。 這意味着您無法真正預測迭代器的功能 - 或者更重要的是數據庫調用 - 的運行順序。 最終結果:不可預知的榮譽學生順序。 如果訂單很重要,請使用.forEachSeries()函數。
啊異步思考的美麗和挫折啊。 嘗試這個:
...............
...............
...............
else
{
console.log("stuObjList.length: " + stuObjList.length);
var j = 0, found = false, step;
for(j = 0; j < stuObjList.length; j++)
{
if(stuObjList[j]['honor_student'] != null)
{
found = true;
step = j;
db.collection("students").findOne({'_id' : stuObjList[j]['_id'];}, function(err, origStuObj)
{
var marker = stuObjList[step]['_id']; // because j's loop has moved on
var major = stuObjList[step]['major'];
process.nextTick(function()
{
callback(stuObjList);
});
});
}
}
if (!found) {
process.nextTick(function()
{
callback(stuObjList);
});
}
}
});
如果您發現“當我完成”步驟變得復雜,請將它們提取到另一個函數,然后從每個點調用它。 在這種情況下,因為它只有2行,所以復制似乎是公平的。
根據要求,您還可以使用下划線的“過濾”方法http://documentcloud.github.com/underscore/#filter
var honor_students = _.filter(stuObjList, function(stud) { return stu['honor_student'] != null });
if (honor_students.length === 0) {
process.nextTick(function() { callback(stuObjList); });
} else {
var honor_students_with_more_data = [];
for (var i = 0; i < honor_students.length; i++) {
db.collection("students").findOne({'_id' : honor_students[i]['_id'];}, function(err, origStuObj) {
// do something with retrieved data
honor_students_with_more_data.push(student_with_more_data);
if (honor_students_with_more_data.length === honor_students.length) {
process.nextTick(function() { callback(stuObjList); });
}
}
}
}
And when the db call returns, the index 'j' is beyond the array index.
在我看來,你需要在每次循環迭代中獲取j的“副本”。 你可以用閉包來做這件事。
if(stuObjList[j]['honor_student'] != null)
{
(function(j_copy){
db.collection("students").findOne({'_id' : stuObjList[j_copy]['_id'];}, function(err, origStuObj)
{
var marker = stuObjList[j_copy]['_id'];
var major = stuObjList[j_copy]['major'];
});
})(j)
}
這樣你就可以在每次迭代時保存j`s狀態。 此狀態保存在每個IIFE內。 您將擁有盡可能多的已保存狀態 - 作為for循環。 當DB返回時:
var marker = stuObjList[j_copy]['_id'];
j_copy將保留原始j的值,它在當前時刻具有
if(stuObjList[j]['honor_student'] != null)
我知道我的解釋技巧非常糟糕,但我希望你能理解我的意思。
編輯:這種方式我們使用立即調用的函數及其范圍來保持j的單獨私有副本。 在每次迭代中,使用自己的私有范圍創建新的IIFE。 在這個范圍內 - 在每個迭代上我們做j_copy = j。 並且這個j_copy可以在IIFE中使用,而不必每次都被for循環覆蓋。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.