2012-05-24 8 views
8

以下のコードを見てください。私は 'stuObjList'と呼ばれるJSONオブジェクトの配列を持っています。特定のフラグが設定された特定のJSONオブジェクトを検索するために配列をループして、より多くのデータを取得するためのdb呼び出しを行いたいとします。(nodejs)、mongodb呼び出しが返るまでFORループを停止する方法

もちろん、FORループはdbコールが戻るまで待機せず、j == lengthの最後に達します。また、dbコールが返ってくると、インデックス 'j'は配列インデックスを超えています。私はnode.jsがどのように動作するかを理解しており、これは予想される動作です。

私の質問は、ここでの回避策は何ですか。私は達成しようとしていることをどのように達成することができますか?ありがとう、--su

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

答えて

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()機能を使用してください。

+0

@Clint ...多くのありがとう。私はこれを試して、それがどのように機能するかを教えてくれるでしょう。 –

+0

@ Clint ...それは働いた。どうもありがとう! –

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行しかなかったので、重複しているように見えました。要件与え

+0

@robrich ...あなたの答えに感謝します。しかし、私は、ここにいくつかの混乱があると思います。私はstuObjList内のすべてのオブジェクトを反復するまで戻りません。あなたのコードから、すぐにIF条件に一致する最初のオブジェクトを満たすように、関数が返されます。そういうことがありますか? –

+0

あなたが正しいとすれば、この解決策はあまりうまくいかず、jQueryのDeferredを見る必要があります。 – robrich

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); }); 
     } 
    } 
    } 
} 
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は、それが

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

の瞬間に私は説明するスキルが非常に悪いことを知っているが、私はあなたがすることを願っている、オリジナルのjの値を保持します私は何を意味するのか理解する。

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

関連する問題