100 votes

Une façon idiomatique d'attendre plusieurs callbacks en Node.js

Supposons que vous ayez besoin d'effectuer certaines opérations qui dépendent d'un fichier temporaire. Puisque nous parlons de Node ici, ces opérations sont évidemment asynchrones. Quelle est la façon idiomatique d'attendre que toutes les opérations se terminent pour savoir quand le fichier temporaire peut être supprimé ? savoir quand le fichier temporaire peut être supprimé ?

Voici un code montrant ce que je veux faire :

do_something(tmp_file_name, function(err) {});
do_something_other(tmp_file_name, function(err) {});
fs.unlink(tmp_file_name);

Mais si je l'écris de cette façon, le troisième appel peut être exécuté avant les deux premiers n'aient eu la chance d'utiliser le fichier. J'ai besoin d'un moyen pour garantir que les deux premiers appels premiers appels sont déjà terminés (ont invoqué leurs callbacks) avant de passer à autre chose sans les appels (et les rendre synchrones en pratique).

J'ai pensé à utiliser des émetteurs d'événements sur les callbacks et à enregistrer un compteur comme récepteur. Le compteur recevrait les événements terminés et compterait combien d'événements opérations sont encore en attente. Lorsque la dernière opération est terminée, il supprime le fichier fichier. Mais il y a le risque d'une condition de course et je ne suis pas sûr que ce soit que ce soit la façon habituelle de faire ce genre de choses.

Comment les gens de Node résolvent-ils ce genre de problème ?

0 votes

Merci pour cette question, j'ai aussi un problème similaire.

95voto

Alfred Points 32190

Mise à jour :

Maintenant, je vous conseille de jeter un coup d'oeil :

  • Promesses

    L'objet Promise est utilisé pour les calculs différés et asynchrones. Une Promise représente une opération qui n'est pas encore terminée, mais qui est attendue dans le futur.

    Une bibliothèque de promesses populaire est oiseau bleu . Je vous conseille de jeter un coup d'œil à pourquoi des promesses .

    Vous devriez utiliser des promesses pour tourner cela :

    fs.readFile("file.json", function (err, val) {
        if (err) {
            console.error("unable to read file");
        }
        else {
            try {
                val = JSON.parse(val);
                console.log(val.success);
            }
            catch (e) {
                console.error("invalid json in file");
            }
        }
    });

    Dans ça :

    fs.readFileAsync("file.json").then(JSON.parse).then(function (val) {
        console.log(val.success);
    })
    .catch(SyntaxError, function (e) {
        console.error("invalid json in file");
    })
    .catch(function (e) {
        console.error("unable to read file");
    });
  • les générateurs : Par exemple via co .

    Bienveillance du flux de contrôle basée sur un générateur pour nodejs et le navigateur, utilisant des promesses, vous permettant d'écrire du code non bloquant d'une manière agréable.

    var co = require('co');
    
    co(function *(){
      // yield any promise
      var result = yield Promise.resolve(true);
    }).catch(onerror);
    
    co(function *(){
      // resolve multiple promises in parallel
      var a = Promise.resolve(1);
      var b = Promise.resolve(2);
      var c = Promise.resolve(3);
      var res = yield [a, b, c];
      console.log(res);
      // => [1, 2, 3]
    }).catch(onerror);
    
    // errors can be try/catched
    co(function *(){
      try {
        yield Promise.reject(new Error('boom'));
      } catch (err) {
        console.error(err.message); // "boom"
     }
    }).catch(onerror);
    
    function onerror(err) {
      // log any uncaught errors
      // co will not throw any errors you do not handle!!!
      // HANDLE ALL YOUR ERRORS!!!
      console.error(err.stack);
    }

Si je comprends bien, je pense que vous devriez jeter un coup d'oeil au très bon asynchrone bibliothèque. Vous devriez notamment jeter un coup d'œil à la série . Juste une copie des extraits de la page Github :

async.series([
    function(callback){
        // do some stuff ...
        callback(null, 'one');
    },
    function(callback){
        // do some more stuff ...
        callback(null, 'two');
    },
],
// optional callback
function(err, results){
    // results is now equal to ['one', 'two']
});

// an example using an object instead of an array
async.series({
    one: function(callback){
        setTimeout(function(){
            callback(null, 1);
        }, 200);
    },
    two: function(callback){
        setTimeout(function(){
            callback(null, 2);
        }, 100);
    },
},
function(err, results) {
    // results is now equals to: {one: 1, two: 2}
});

En outre, cette bibliothèque peut également fonctionner dans le navigateur.

22 votes

En fait, j'ai fini par utiliser async.parallel, puisque les opérations sont indépendantes et que je ne voulais pas les faire attendre sur les précédentes.

22voto

Michael Dillon Points 18741

La méthode la plus simple consiste à incrémenter un compteur de nombres entiers lorsque vous lancez une opération asynchrone, puis, dans le callback, à décrémenter le compteur. Selon la complexité de l'opération, la callback peut vérifier que le compteur est à zéro, puis supprimer le fichier.

Un peu plus complexe serait de maintenir une liste d'objets, et chaque objet aurait tous les attributs dont vous avez besoin pour identifier l'opération (il pourrait même s'agir de l'appel de la fonction) ainsi qu'un code d'état. Les rappels définiraient le code d'état à "completed".

Ensuite, vous auriez une boucle qui attend (en utilisant process.nextTick ) et vérifie si toutes les tâches sont terminées. L'avantage de cette méthode par rapport au compteur, est que s'il est possible que toutes les tâches en suspens soient terminées, avant que toutes les tâches soient émises, la technique du compteur vous ferait supprimer le fichier prématurément.

11voto

goofballLogic Points 508
// simple countdown latch
function CDL(countdown, completion) {
    this.signal = function() { 
        if(--countdown < 1) completion(); 
    };
}

// usage
var latch = new CDL(10, function() {
    console.log("latch.signal() was called 10 times.");
});

7voto

Ricardo Tomasi Points 13398

Il n'y a pas de solution "native", mais il existe une millions de bibliothèques de contrôle de flux pour le nœud. Vous pourriez aimer Step :

Step(
  function(){
      do_something(tmp_file_name, this.parallel());
      do_something_else(tmp_file_name, this.parallel());
  },
  function(err) {
    if (err) throw err;
    fs.unlink(tmp_file_name);
  }
)

Ou, comme Michael l'a suggéré, les compteurs pourraient être une solution plus simple. Jetez un coup d'oeil à ceci maquette de sémaphore . Tu l'utiliserais comme ça :

do_something1(file, queue('myqueue'));
do_something2(file, queue('myqueue'));

queue.done('myqueue', function(){
  fs.unlink(file);
});

2voto

alienhard Points 5837

La solution la plus simple est d'exécuter les commandes do_something* et unlink en séquence comme suit :

do_something(tmp_file_name, function(err) {
    do_something_other(tmp_file_name, function(err) {
        fs.unlink(tmp_file_name);
    });
});

À moins que, pour des raisons de performances, vous ne souhaitiez exécuter do_quelquechose() et do_quelquechose_autre() en parallèle, je vous suggère de rester simple et de procéder ainsi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X