8

我正在处理下面的代码片段。我有一个名为“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);
      });
    }
  }
}
});
4

4 回答 4

8

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

于 2012-05-24T12:26:15.193 回答
1

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

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

于 2012-05-24T03:07:58.087 回答
1

给定要求,您还可以使用下划线的“过滤器”方法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); });
      }
    }
  }
}
于 2012-05-24T08:04:00.520 回答
0
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 循环覆盖。

于 2016-12-31T15:09:00.147 回答