【问题标题】:In Nodejs, how to stop a FOR loop until MongoDB call returns在 Nodejs 中,如何在 MongoDB 调用返回之前停止 FOR 循环
【发布时间】:2012-05-24 03:00:36
【问题描述】:

我正在编写下面的代码 sn-p。我有一个名为“stuObjList”的 JSON 对象数组。我想循环遍历数组以查找具有特定标志集的特定 JSON 对象,然后进行数据库调用以检索更多数据。

当然,FOR 循环不会等待数据库调用返回,而是以 j == length 结束。并且当数据库调用返回时,索引“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);
      });
    }
  }
}
});

【问题讨论】:

    标签: javascript node.js mongodb express


    【解决方案1】:

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

    【讨论】:

    • @Clint...非常感谢。我会试试这个,让你知道结果如何。
    【解决方案2】:

    啊,异步思考的美妙和挫败感。试试这个:

    ...............
    ...............
    ...............
    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 行,所以重复似乎是公平的。

    【讨论】:

    • @robrich...感谢您的回答。但我认为,这里有些混乱。在遍历 stuObjList 中的所有对象之前,我不想返回。从您的代码看起来,只要我遇到第一个符合 IF 条件的对象,该函数就会返回。有这样做的吗?
    • 那你是对的,这个解决方案不会那么好用,我们需要看看 jQuery 的 Deferred。
    【解决方案3】:

    根据需求,也可以使用下划线的“过滤”方法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); });
          }
        }
      }
    }
    

    【讨论】:

      【解决方案4】:
      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 循环一样多的已保存状态。当数据库返回时:

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

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

      的那一刻拥有
      if(stuObjList[j]['honor_student'] != null)
      

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

      编辑: 这样,我们使用立即调用的函数及其作用域来保存 j 的单独私有副本。 在每次迭代中,都会使用自己的私有范围创建新的 IIFE。在这个范围内 - 在每次迭代中,我们执行 j_copy = j。并且这个 j_copy 可以在 IIFE 内部使用,而不会每次都被 for 循环覆盖。

      【讨论】:

        猜你喜欢
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-20
        相关资源
        最近更新 更多