在nodejs中,如何停止FOR循环直到mongodb调用返回

请看下面的代码片段。 我有一个名为“stuObjList”的JSON对象数组。 我想通过数组来循环查找具有特定标志集的特定JSON对象,然后进行数据库调用来检索更多的数据。

当然,FOR循环不会等待db调用返回,并以j ==长度结束。 当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); }); } } } }); 

“ asynchronous ”是一个非常stream行的模块,用于抽象出asynchronous循环,使代码更易于读取/维护。 例如:

 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()函数。

啊asynchronous思考的美丽和沮丧。 尝试这个:

 ............... ............... ............... 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); }); } } }); 

如果你发现你的“当我完成”的步骤变得越来越复杂,提取他们到另一个function,并从每个点调用它。 在这种情况下,因为它只有2行,似乎重复。

给定的要求,你也可以使用下划线的“filter”方法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的状态。 这个状态保存在每个IIFE内部。 你将有许多保存的状态 – for循环。 当DB返回时:

 var marker = stuObjList[j_copy]['_id']; 

j_copy将保留原始j的值,它在当前的值

 if(stuObjList[j]['honor_student'] != null) 

我知道我的解释能力很差,但我希望你能理解我的意思。

编辑:这种方式我们正在使用立即调用的函数及其范围,以保持独立的私人副本的j。 在每次迭代中,新的IIFE都会创build自己的私有范围。 在这个范围内 – 每次迭代我们都做j_copy = j。 而这个j_copy可以在IIFE中使用,而不会被每次for循环覆盖。