2011-12-20 11 views
6

Jestem nowicjuszem w usłudze Node i próbuję upewnić się, że używam rozsądnych projektów dla aplikacji internetowej opartej na JSON.Najlepszy wzorzec obsługi pętli asynchronicznej w pliku Node.js

Mam kilka danych przechowywanych w Redis i odbieram je przez węzeł, przesyłając wyniki, gdy pochodzą z Redis. Tutaj jest dobrym przykładem tego, co robię:

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     res.write("["); 
     replies.forEach(function (reply, i) { 
      rc.get(reply, function(err, reply) { 
       res.write(reply); 
       if (i == replies.length-1) { 
        res.write("]"); 
        res.end(); 
       } 
       else 
        res.write(","); 
      }); 
     }); 
    }); 
}); 

Zasadniczo jestem coraz zestaw kluczy z Redis, a następnie każdy z prośbą, streaming z wyniku w pół-ręcznie utworzonego JSON (struny wychodzi Redis są już w JSON). Teraz to działa ładnie, ale nie mogę przestać myśleć, że i == replies.length-1 jest trochę zaniedbany?

Mogłem to wszystko zrobić z mgetem w Redis, ale to nie jest naprawdę punkt, który próbuję zdobyć; najlepiej obsłużyć pętlę asynchroniczną za pomocą forEach, przesyłając strumieniowo dane wyjściowe i płynnie zamykając połączenie za pomocą polecenia res.end z zapętleniem.

Czy to najlepszy sposób, czy też istnieje bardziej elegancki wzór, który mogę zastosować?

+0

do głębokich zagnieżdżonych wywołań zwrotnych funkcyjnych chciałbym użyć asynchronicznie Biblioteka .js. – BRampersad

Odpowiedz

6

Powyższy kod może nie być zgodny z oczekiwaniami. Wywołujesz każdą sekwencję .get(), ale mogą one nie oddzwaniać po kolei - więc wyniki mogą być przesyłane strumieniowo w dowolnej kolejności. Jeśli chcesz przesyłać strumieniowo wyniki zamiast zbierać je w pamięci, musisz .get() w sekwencji.

Wydaje mi się, że wiele łatwiejsze jest z tego powodu: caolan’s async library. Oto jeden ze sposobów można użyć go uzyskać każdy element w sekwencji (ostrzeżenie, niesprawdzone):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     var i = 0; 
     res.write("["); 
     async.forEachSeries(replies, function(reply, callback){ 
      rc.get(reply, function(err, reply) { 
       if (err){ 
        callback(err); 
        return; 
       } 
       res.write(reply); 
       if (i < replies.length) { 
        res.write(","); 
       } 
       i++; 
       callback(); 
      }); 
     }, function(err){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end(']'); 
      } 
     }); 
    }); 
}); 

Jeśli nie dbają o porządek, wystarczy użyć async.forEach() zamiast.

Jeśli nie przeszkadza zbieranie wyników i chcesz je zwrócić w sekwencji, można użyć async.map() tak (ostrzeżenie, również niesprawdzone):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     async.map(replies, rc.get.bind(rc), function(err, replies){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end('[' + replies.join(',') + ']'); 
      } 
     }); 
    }); 
}); 
+0

To świetnie; dzięki za kod; Wypróbowałem bibliotekę asynchroniczną i działa idealnie. Zamówienie nie ma znaczenia, ale rozwiązanie mapy wygląda o wiele bardziej elegancko, więc mogę po prostu trzymać się tego. –

+0

Próbuję sprawdzić, jak działa parametr rc.get.bind (rc) w wywołaniu funkcji map; to świetny sposób robienia tego. Czy możesz wyjaśnić nieco, jak to działa dokładnie? –

+0

@mjs [bind] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind) jest częścią ECMAScript 5, i zwraca kopię funkcji, która jest "związana" z szczególności "tej" wartości. W tym przypadku oznacza to, że gdy async.map wywoła 'get()', będzie miało 'rc' jako jego wartość' this'. – s4y

3

Można użyć biblioteki async, zapewnia kilka przydatnych metod zapętlenie, takie jak forEach:

foreach (arr, iterator, callback)

Stosuje iterator dla każdego elementu w tablica, równolegle. Iterator jest wywoływany z elementem z listy i wywołania zwrotnego dla po jego zakończeniu. Jeśli iterator przekaże błąd do tego wywołania zwrotnego , główne wywołanie zwrotne funkcji forEach zostanie natychmiast wywołane z błędem.

Należy zauważyć, że ponieważ ta funkcja stosuje iterator do każdej pozycji w numerze równoległym , nie ma gwarancji, że funkcje iteratora zostaną zakończone zgodnie z kolejnością w .

Przykład

// assuming openFiles is an array of file names and saveFile is a function 
// to save the modified contents of that file: 

async.forEach(openFiles, saveFile, function(err){ 
    // if any of the saves produced an error, err would equal that error 
}); 
+0

Spojrzę na tę bibliotekę; dzięki za wskaźnik. –

1

ale nie mogę oprzeć się myśli, że ja == replies.length-1 jest trochę zaniedbany?

Słyszałem, że wiele osób tak mówi. W ten sposób chciałbym zrobić to ręcznie:

app.get("/facility", function(req, res, next) { 
    rc.keys("FACILITY*", function(err, replies) { 
    if (err) return next(err); 
    var pending = replies.length; 
    res.write("["); 
    replies.forEach(function (reply) { 
     rc.get(reply, function(err, reply) { 
     res.write(reply); 
     if (!--pending) { 
      res.write("]"); 
      return res.end(); 
     } 
     res.write(","); 
     }); 
    }); 
    }); 
}); 

Oczywiście robi to ręcznie nie jest zbyt ładna, dlatego ludzie mają to oderwane do biblioteki lub innej funkcji. Ale podoba ci się to, czy nie, w ten sposób robisz asynchroniczną pętlę równoległą. :)

Możesz użyć wspomnianej wcześniej biblioteki async, aby ukryć paskudne wnętrzności.

+0

To jest lepszy sposób robienia tego; Dziękuję za to. –

Powiązane problemy