2012-05-24 6 views
8

Proszę spojrzeć na fragment kodu poniżej. Mam tablicę obiektów JSON o nazwie "stuObjList". Chcę przechodzić przez tablicę, aby znaleźć określone obiekty JSON z określonym zestawem flag, a następnie wykonać wywołanie db, aby pobrać więcej danych.w nodejs, jak zatrzymać pętlę FOR, aż wywołanie mongodb zwróci

Oczywiście, pętla FOR nie czeka na wywołanie db i osiąga koniec z j == długość. A gdy wywołanie db zwróci, indeks "j" wykracza poza indeks tablicy. Rozumiem, jak działa plik node.js i jest to oczekiwane zachowanie.

Moje pytanie brzmi, co tu się dzieje. Jak mogę osiągnąć to, co próbuję osiągnąć? Dzięki, --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); 
     }); 
    } 
    } 
} 
}); 

Odpowiedz

8

async” jest bardzo popularny moduł abstrahując dala asynchroniczny zapętlenie i czyni Twój kod łatwiejszy do odczytu/utrzymaniu. Na przykład:

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

więc można go używać tak:

getHonorStudentsFrom(studentObjs, function(err, honorStudents) { 
    if(err) { 
     // Handle the error 
     return; 
    } 

    // Do something with honroStudents 
}); 

Zauważ, że .forEach() wezwie swój iterator dla każdego elementu w stuObjList „równolegle” (to znaczy, że nie będzie czekać dla jednej funkcji iteratora, aby zakończyć wywołanie dla jednego elementu tablicy przed wywołaniem go na następnym elemencie tablicy). Oznacza to, że nie można naprawdę przewidzieć kolejność, w której działa iterator - lub, co ważniejsze, wywołania bazy danych - zostanie uruchomiony. Wynik końcowy: nieprzewidywalny porządek studentów honorowych. Jeśli zamówienie ma znaczenie, użyj funkcji .forEachSeries().

+0

@ Clint ... wielkie dzięki. Wypróbuję to i dam ci znać, jak to działa. –

+0

@Clint ... to działało. Wielkie dzięki! –

1

Ah piękno i frustracja myślenia asynchronicznie. Spróbuj tego:

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

Jeśli znajdziesz swoją „kiedy skończę” kroki są coraz skomplikowane, wyodrębnić je do innej funkcji, a po prostu zadzwonić z każdego miejsca. W tym przypadku, ponieważ było to tylko 2 wiersze, zdawało się, że należy je powielać.

+0

@robrich ... dziękuję za odpowiedź. Ale myślę, że tutaj jest trochę zamieszania. NIE chcę wracać, dopóki nie przejdę przez WSZYSTKIE obiekty w stuObjList. Wygląda na to, że jak tylko pierwszy obiekt spełnia warunek IF, funkcja wróci. Czy to możliwe? –

+0

Wtedy masz rację, to rozwiązanie nie będzie działać tak dobrze i będziemy musieli spojrzeć na Odroczoną jQuery. – robrich

1

podane wymagania, można również użyć „filtr” metoda podkreślenia jest 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. 

Wydaje mi się, że trzeba wykonać "kopię" j w każdej iteracji pętli. Możesz to zrobić z zamknięciami.

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) 

} 

W ten sposób zapisujesz stan J w każdej iteracji. Ten stan jest zapisywany wewnątrz każdego IIFE. Będziesz miał tyle zapisanych stanów - jak dla pętli for. Gdy DB zwraca:

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

j_copy zachowa wartość oryginalnego j, który posiada w chwili

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

Znam moje umiejętności wyjaśniania są bardzo złe, ale mam nadzieję, że można rozumiesz co mam na myśli.

Edycja: W ten sposób używamy natychmiast wywoływanej funkcji i jej zakresu, aby zachować oddzielną prywatną kopię j. Przy każdej iteracji tworzony jest nowy IIFE z własnym zasięgiem prywatnym. W tym zakresie - w każdym dla iteracji wykonujemy j_copy = j.I to j_copy może być użyte wewnątrz IIFE bez każdorazowego nadpisywania przez pętlę for.

Powiązane problemy