web-dev-qa-db-fra.com

Utiliser une cascade asynchrone dans node.js

J'ai 2 fonctions que j'exécute de manière asynchrone. J'aimerais les écrire en utilisant un modèle en cascade. 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 

Toute idée de comment puis-je l'écrire en utilisant une cascade, pour que je puisse contrôler cela? S'il vous plaît écrivez-moi quelques exemples parce que je suis nouveau sur node.js

41
user3502786

Commencez par identifier les étapes et les écrire en tant que fonctions asynchrones (en 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 du fichier console.log 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 ici, je vais utiliser async.waterfall pour centraliser le traitement des erreurs au même endroit.

Veillez également à ce que si vous avez (si/else/switch/...) des branches dans une fonction asynchrone, elle appelle toujours le rappel une (et une seule) fois.

Branchez tout avec async.waterfall

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

Exemple propre

Le code précédent était excessivement prolixe pour clarifier les explications. Voici un exemple complètement nettoyé:

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 laissé les noms de fonction car cela facilite la lisibilité et facilite le débogage avec des outils tels que chrome debugger.

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

59
Volune

Avant tout, assurez-vous de lisez la documentation concernant async.waterfall .

Maintenant, il y a quelques éléments clés sur le flux de contrôle de cascade:

  1. Le flux de contrôle est spécifié par un tableau de fonctions à appeler en tant que premier argument et par un rappel "complet" lorsque le flux est terminé en tant que second argument.
  2. Le tableau de fonctions est invoqué en séries (par opposition à parallèle).
  3. Si une erreur (généralement nommée err) est rencontrée à une opération quelconque du tableau de flux, elle court-circuitera et invoquera immédiatement le "complet"/"finish"/"done" callback.
  4. Les arguments de la fonction précédemment exécutée sont appliqué à la fonction suivante dans le flux de contrôle, dans l'ordre, et un rappel "intermédiaire" est fourni comme dernier argument. Remarque: La première fonction n'a que ce rappel "intermédiaire" et le rappel "complet" aura les arguments de la dernière fonction invoquée dans le flux de contrôle (en tenant compte des erreurs éventuelles), mais avec un argument err ajouté au lieu d'un rappel "intermédiaire" qui est ajouté.
  5. Les rappels pour chaque opération individuelle (j’appelle ceci cbAsync dans mes exemples) devraient être invoqués lorsque vous êtes prêt à passer à autre chose: le premier paramètre sera une erreur, le cas échéant, et le deuxième (troisième, quatrième ... etc.) sera le paramètre que vous souhaitez transmettre à l’opération suivante.

Le premier objectif est de faire fonctionner votre code presque mot pour mot, parallèlement à l'introduction de async.waterfall. J'ai décidé de retirer tous vos console.log _ et simplifie la gestion de vos 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 des opérations. Cela le place dans de plus petits morceaux de code orientés sur une seule opération. Je ne vais pas 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 a fait beaucoup de cas avec l'utilisation de quelques bind astuces pour diminuer la pile d'appels et augmenter la lisibilité (IMO), également non testées:

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
zamnuts

En principe, 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 des processeurs) sont généralement asynchrones. La boucle d'événement (pour simplifier, est une boucle qui vérifie en permanence l'exécution des tâches). ) peut invoquer la fonction juste en dessous de la première, sans être bloqué pour une réponse. Si vous connaissez d'autres langages tels que C ou Java, vous pouvez penser à une fonction asynchrone comme une fonction qui s'exécute sur un autre thread (ce n'est pas forcément vrai en javascript, mais le programmeur ne devrait pas s'en soucier) et quand l'exécution se termine thread notifie à la tâche principale (la boucle d’événement une) que le travail est terminé et qu’il a les résultats.

Comme indiqué précédemment, une fois que la première fonction a terminé son travail, elle doit pouvoir indiquer que son travail est terminé et invoque la fonction de rappel que vous lui transmettez. 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'à la fin de asyncFunction1, puis la fonction de rappel passée en tant que dernier paramètre à asyncFunction1 est appelée pour traiter les données si aucune erreur ne se produit.

Ainsi, pour que deux ou plusieurs fonctions asynchrones s'exécutent l'une après l'autre uniquement à la fin, vous devez les appeler dans leurs fonctions de rappel:

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 de asyncTask1 et result2 est la valeur de retour pour asyncTask2. Vous pouvez ainsi imbriquer le nombre de fonctions asynchrones souhaitées.

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

console.log("File successfully written");
2
MastErAldo