8

以下のコードスニペットに取り組んでいます。「stuObjList」という JSON オブジェクトの配列があります。配列をループして、特定のフラグが設定された特定の JSON オブジェクトを検索し、データベース呼び出しを行ってさらにデータを取得したいと考えています。

もちろん、FOR ループはデータベース呼び出しが戻るのを待たず、with 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 内の各要素に対して反復子関数を「並行して」呼び出すことに注意してください (つまり、1 つの反復子関数が 1 つの配列要素に対して呼び出されるのを待たずに、次の配列要素に対して呼び出します)。つまり、反復子関数 (さらに重要なことには、データベース呼び出し) が実行される順序を実際に予測することはできません。結末は予測不可能な優等生の序列。順序が重要な場合は、.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 ループと同じ数の状態が保存されます。DB が返された場合:

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

j_copy は、元の j の値を保持します。

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

私の説明能力が非常に低いことは承知していますが、私の言いたいことを理解していただければ幸いです。

編集: このようにして、すぐに呼び出される関数とそのスコープを使用して、j の個別のプライベート コピーを保持します。反復ごとに、新しい IIFE が独自のプライベート スコープで作成されます。このスコープでは、反復ごとに j_copy = j を実行します。そして、この j_copy は、毎回 for ループによって上書きされることなく、IIFE 内で使用できます。

于 2016-12-31T15:09:00.147 に答える