Utilisation de la chute D'eau asynchrone dans le noeud.js

j'ai 2 fonctions que j'exécute de façon asynchrone. J'aimerais les écrire en utilisant le modèle de chute d'eau. La chose est, je ne sais pas comment..

Voici mon code :

var fs = require('fs');
function updateJson(ticker, value) {
  //var stocksJson = JSON.parse(fs.readFileSync("stocktest.json"));
  fs.readFile('stocktest.json', function(error, file) {
    var stocksJson =  JSON.parse(file);

    if (stocksJson[ticker]!=null) {
      console.log(ticker+" price : " + stocksJson[ticker].price);
      console.log("changing the value...")
      stocksJson[ticker].price =  value;

      console.log("Price after the change has been made -- " + stocksJson[ticker].price);
      console.log("printing the the Json.stringify")
      console.log(JSON.stringify(stocksJson, null, 4));
      fs.writeFile('stocktest.json', JSON.stringify(stocksJson, null, 4), function(err) {  
        if(!err) {
          console.log("File successfully written");
        }
        if (err) {
          console.error(err);
        }
      }); //end of writeFile
    } else {
      console.log(ticker + " doesn't exist on the json");
    }
  });
} // end of updateJson 

une idée de comment je peux l'écrire en utilisant waterfall, donc je vais être capable de contrôler ça? Écrivez-moi quelques exemples parce que je suis nouveau sur le nœud.js

34
demandé sur Talha Awan 2014-09-07 01:46:50

3 réponses

identifiez D'abord les étapes et écrivez - les comme des fonctions asynchrones (prenant un argument de rappel)

  • lire le fichier

    function readFile(readFileCallback) {
        fs.readFile('stocktest.json', function (error, file) {
            if (error) {
                readFileCallback(error);
            } else {
                readFileCallback(null, file);
            }
        });
    }
    
  • traiter le fichier (j'ai supprimé la plupart de la console.journal dans les exemples)

    function processFile(file, processFileCallback) {
        var stocksJson = JSON.parse(file);
        if (stocksJson[ticker] != null) {
            stocksJson[ticker].price = value;
            fs.writeFile('stocktest.json', JSON.stringify(stocksJson, null, 4), function (error) {
                if (err) {
                    processFileCallback(error);
                } else {
                    console.log("File successfully written");
                    processFileCallback(null);
                }
            });
        }
        else {
            console.log(ticker + " doesn't exist on the json");
            processFileCallback(null); //callback should always be called once (and only one time)
        }
    }
    

notez que je n'ai pas traité d'erreur spécifique ici, je vais profiter d'async.chute d'eau pour centraliser le traitement des erreurs au même endroit.

faites également attention si vous avez (if/else//interrupteur...) branches dans une fonction asynchrone, il appelle toujours le rappel une (et seulement une) fois.

branchez tout avec async.cascade

async.waterfall([
    readFile,
    processFile
], function (error) {
    if (error) {
        //handle readFile error or processFile error here
    }
});

Nettoyer exemple

le code précédent était excessivement verbeux pour rendre les explications plus claires. Voici une nettoyés exemple:

async.waterfall([
    function readFile(readFileCallback) {
        fs.readFile('stocktest.json', readFileCallback);
    },
    function processFile(file, processFileCallback) {
        var stocksJson = JSON.parse(file);
        if (stocksJson[ticker] != null) {
            stocksJson[ticker].price = value;
            fs.writeFile('stocktest.json', JSON.stringify(stocksJson, null, 4), function (error) {
                if (!err) {
                    console.log("File successfully written");
                }
                processFileCallback(err);
            });
        }
        else {
            console.log(ticker + " doesn't exist on the json");
            processFileCallback(null);
        }
    }
], function (error) {
    if (error) {
        //handle readFile error or processFile error here
    }
});

j'ai quitté les noms de fonction parce qu'ils facilitent la lisibilité et le débogage avec des outils comme le débogueur chrome.

Si vous utilisez soulignement (sur npm), vous pouvez également remplacer la première fonction avec _.partial(fs.readFile, 'stocktest.json')

52
répondu Volune 2014-09-06 22:24:37

d'Abord et avant tout, assurez-vous d' lire la documentation concernant async.waterfall.

maintenant, il y a quelques parties clés au sujet du débit de contrôle de la chute d'eau:

  1. le flux de contrôle est spécifié par un tableau de fonctions pour l'invocation comme premier argument, et un rappel "complet" lorsque le flux est terminé comme second argument.
  2. le tableau des fonctions est invoqué dans série (par opposition à parallèle.)
  3. si une erreur (habituellement nommée err) est rencontré à n'importe quelle opération dans le tableau de flux, il court-circuit et invoquer immédiatement le "complet"/"fini"/"fait" callback.
  4. les Arguments de la fonction exécutée précédemment sont appliquer à la fonction suivante dans le flux de contrôle, dans l'ordre, et un rappel "intermédiaire" est fourni comme dernier argument. Note: la première fonction a seulement ce rappel "intermédiaire", et le "complet"" rappel aura les arguments de la dernière fonction invoquée dans le flux de contrôle (en tenant compte de toutes les erreurs), mais avec un err argument préprogrammé au lieu d'un rappel" intermédiaire " qui est ajouté.
  5. les callbacks pour chaque opération individuelle (Je l'appelle cbAsync dans mes exemples) doit être invoquée lorsque vous êtes prêt à passer: Le premier paramètre est une erreur, le cas échéant, et la deuxième (troisième, quatrième... etc.) paramètre sera toutes les données que vous souhaitez passer à la opération ultérieure.

le premier objectif est de faire fonctionner votre code presque mot à mot avec l'introduction de async.waterfall. J'ai décidé de supprimer tous vos console.log déclarations et simplifié votre traitement des erreurs. Voici la première itération (code non testé):

var fs = require('fs'),
    async = require('async');

function updateJson(ticker,value) {
    async.waterfall([ // the series operation list of `async.waterfall`
        // waterfall operation 1, invoke cbAsync when done
        function getTicker(cbAsync) {
            fs.readFile('stocktest.json',function(err,file) {
                if ( err ) {
                    // if there was an error, let async know and bail
                    cbAsync(err);
                    return; // bail
                }
                var stocksJson = JSON.parse(file);
                if ( stocksJson[ticker] === null ) {
                    // if we don't have the ticker, let "complete" know and bail
                    cbAsync(new Error('Missing ticker property in JSON.'));
                    return; // bail
                }
                stocksJson[ticker] = value;
                // err = null (no error), jsonString = JSON.stringify(...)
                cbAsync(null,JSON.stringify(stocksJson,null,4));    
            });
        },
        function writeTicker(jsonString,cbAsync) {
            fs.writeFile('stocktest.json',jsonString,function(err) {
                cbAsync(err); // err will be null if the operation was successful
            });
        }
    ],function asyncComplete(err) { // the "complete" callback of `async.waterfall`
        if ( err ) { // there was an error with either `getTicker` or `writeTicker`
            console.warn('Error updating stock ticker JSON.',err);
        } else {
            console.info('Successfully completed operation.');
        }
    });
}

la deuxième itération divise un peu plus le flux d'opération. Il le met dans de plus petits morceaux de code à une seule opération. Je ne vais pas le commenter, il parle pour lui-même (encore une fois, non testé):

var fs = require('fs'),
    async = require('async');

function updateJson(ticker,value,callback) { // introduced a main callback
    var stockTestFile = 'stocktest.json';
    async.waterfall([
        function getTicker(cbAsync) {
            fs.readFile(stockTestFile,function(err,file) {
                cbAsync(err,file);
            });
        },
        function parseAndPrepareStockTicker(file,cbAsync) {
            var stocksJson = JSON.parse(file);
            if ( stocksJson[ticker] === null ) {
                cbAsync(new Error('Missing ticker property in JSON.'));
                return;
            }
            stocksJson[ticker] = value;
            cbAsync(null,JSON.stringify(stocksJson,null,4));
        },
        function writeTicker(jsonString,cbAsync) {
            fs.writeFile('stocktest.json',jsonString,,function(err) {
                cbAsync(err);
            });
        }
    ],function asyncComplete(err) {
        if ( err ) {
            console.warn('Error updating stock ticker JSON.',err);
        }
        callback(err);
    });
}

La dernière itération de court-mains beaucoup de cela avec l'utilisation de certains bind trucs pour diminuer la pile d'appels et augmenter la lisibilité (IMO), également non testé:

var fs = require('fs'),
    async = require('async');

function updateJson(ticker,value,callback) {
    var stockTestFile = 'stocktest.json';
    async.waterfall([
        fs.readFile.bind(fs,stockTestFile),
        function parseStockTicker(file,cbAsync) {
            var stocksJson = JSON.parse(file);
            if ( stocksJson[ticker] === null ) {
                cbAsync(new Error('Missing ticker property in JSON.'));
                return;
            }
            cbAsync(null,stocksJson);
        },
        function prepareStockTicker(stocksJson,cbAsync) {
            stocksJson[ticker] = value;
            cbAsync(null,JSON.stringify(stocksJson,null,4));
        },
        fs.writeFile.bind(fs,stockTestFile)
    ],function asyncComplete(err) {
        if ( err ) {
            console.warn('Error updating stock ticker JSON.',err);
        }
        callback(err);
    });
}
14
répondu zamnuts 2014-09-06 22:36:57

fondamentalement, les fonctions nodejs (et plus généralement javascript) qui nécessitent un certain temps d'exécution (que ce soit pour le traitement des e/s ou cpu) sont généralement asynchrones, de sorte que la boucle d'événement (pour la rendre simple est une boucle qui vérifie en continu les tâches à exécuter) peut invoquer la fonction juste en dessous de la première, sans se bloquer pour une réponse. Si vous êtes familier avec D'autres langages comme C ou Java, vous pouvez penser à une fonction asynchrone comme une fonction qui tourne sur un autre thread (c'est pas nécessairement vrai dans javascript, mais le programmeur ne devrait pas s'en soucier) et quand l'exécution se termine ce thread avertit le principal (la boucle d'événement un) que le travail est fait et il a les résultats.

comme dit une fois que la première fonction a terminé son travail, elle doit être capable de notifier que son travail est terminé et elle le fait en invoquant la fonction de rappel que vous lui passez. pour faire un exemple:

var callback = function(data,err)
{
   if(!err)
   {
     do something with the received data
   }
   else
     something went wrong
}


asyncFunction1(someparams, callback);

asyncFunction2(someotherparams);

le flux d'exécution appellerait: asyncFunction1, asyncFunction2 et toutes les fonctions ci-dessous jusqu'à ce que asyncFunction1 se termine, puis la fonction de rappel qui est passé comme dernier paramètre à asyncFunction1 est appelé à faire quelque chose avec les données si aucune erreur ne s'est produite.

donc, pour faire exécuter 2 ou plus de fonctions asynchrones une à la suite de l'autre seulement quand elles se terminent, vous devez les appeler à l'intérieur de leurs fonctions de callback:

function asyncTask1(data, function(result1, err)
{
   if(!err)
     asyncTask2(data, function(result2, err2)
     {
           if(!err2)
        //call maybe a third async function
           else
             console.log(err2);
     });
    else
     console.log(err);
});

result1 est la valeur de retour d'asyncTask1 et result2 est la valeur de retour d'asyncTask2. Vous peut de cette façon nicher combien de fonctions asynchrones vous voulez.

dans votre cas, si vous voulez qu'une autre fonction soit appelée après updateJson (), vous devez l'appeler après cette ligne:

console.log("File successfully written");
2
répondu MastErAldo 2015-05-22 20:05:44