в nodejs, как остановить цикл FOR, пока не вернется вызов mongodb

пожалуйста, посмотрите на код ниже. У меня есть массив объектов JSON под названием "stuObjList". Я хочу пройти через массив, чтобы найти определенные объекты JSON с определенным набором флагов, а затем сделать вызов db для получения дополнительных данных.

конечно, цикл FOR не ждет возврата вызова db и достигает конца с длиной j==. И когда вызов db возвращается, индекс " j " выходит за пределы индекса массива. Я понимаю, как это делается.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);
      });
    }
  }
}
});

4 ответов


"асинхронные " - очень популярный модуль для абстрагирования асинхронного цикла и облегчения чтения/обслуживания вашего кода. Например:

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 на каждой итерации. Это состояние сохраняется внутри каждой жизни. У вас будет столько же сохраненных состояний - как и для циклов. Когда БД возвращается:

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

j_copy сохранит значение исходного j, которое он имеет в момент

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

Я знаю, что мои навыки объяснения очень плохие, но я надеюсь, вы понимаете, что я имею в виду.

изменить: Таким образом, мы используем немедленно вызываемую функцию и ее область для хранения отдельной частной копии j. На каждой итерации создается новая IIFE со своей собственной частной областью. В этой области-на каждой итерации мы делаем j_copy = j. И этот j_copy может использоваться внутри IIFE без перезаписи циклом for каждый раз.