Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Async waterfall in node.js

I have 2 functions that I'm running asynchronously. I'd like to write them using waterfall model. The thing is, I don't know how..

Here is my 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  

Any idea how can I write it using waterfall, so i'll be able to control this? Please write me some examples because I'm new to node.js

like image 370
Alex Brodov Avatar asked Sep 06 '14 21:09

Alex Brodov


People also ask

How do you use async await in async waterfall?

If you use the async keyword before a function definition, you can then use await within the function. When you await a promise, the function is paused in a non-blocking way until the promise settles. If the promise fulfills, you get the value back.

How do I use async parallel in node JS?

Example of async. parallel([ function(callback) { setTimeout(function() { console. log('Task One'); callback(null, 1); }, 200); }, function(callback) { setTimeout(function() { console. log('Task Two'); callback(null, 2); }, 100); } ], // optional callback function(err, results) { console.

How do I use async in node JS?

With Node v8, the async/await feature was officially rolled out by the Node to deal with Promises and function chaining. The functions need not to be chained one after another, simply await the function that returns the Promise. But the function async needs to be declared before awaiting a function returning a Promise.

How does async eachSeries work?

eachSeries(coll, iteratee, callbackopt) import eachSeries from 'async/eachSeries'; The same as each but runs only a single async operation at a time. Note, that unlike each , this function applies iteratee to each item in series and therefore the iteratee functions will complete in order.

What is async async waterfall?

Async is a JavaScript library that allows you to control the flow of asynchronous JavaScript code. In this tutorial we are going to explore the Async.waterfall method to run asynchronous functions "in-order".

What is control flow in async JS?

Control flow is the order in which individual statements, instructions, or function calls of an imperative program are executed or evaluated. This article goes through async flow functions using Async.js to control the flow of task executions. The functions include serial, parallel, waterfall, queues, etc.

What is the difference between promise and async waterfall?

So here, essentially, the async.waterfall is conceptually similar to promise.then (...) They have the same dependency nature but different code styles That said, I hope you found this tutorial useful!

How do you handle asynchronous in JavaScript?

The OG way of handling the asynchronous nature of JavaScript engines was through callbacks. Callbacks are basically functions which will be executed, usually, at the end of synchronous or I/O blocking operations.


2 Answers

First identify the steps and write them as asynchronous functions (taking a callback argument)

  • read the file

    function readFile(readFileCallback) {     fs.readFile('stocktest.json', function (error, file) {         if (error) {             readFileCallback(error);         } else {             readFileCallback(null, file);         }     }); } 
  • process the file (I removed most of the console.log in the examples)

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

Note that I did no specific error handling here, I'll take benefit of async.waterfall to centralize error handling at the same place.

Also be careful that if you have (if/else/switch/...) branches in an asynchronous function, it always call the callback one (and only one) time.

Plug everything with async.waterfall

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

Clean example

The previous code was excessively verbose to make the explanations clearer. Here is a full cleaned example:

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

I left the function names because it helps readability and helps debugging with tools like chrome debugger.

If you use underscore (on npm), you can also replace the first function with _.partial(fs.readFile, 'stocktest.json')

like image 132
Volune Avatar answered Sep 28 '22 00:09

Volune


First and foremost, make sure you read the documentation regarding async.waterfall.

Now, there are couple key parts about the waterfall control flow:

  1. The control flow is specified by an array of functions for invocation as the first argument, and a "complete" callback when the flow is finished as the second argument.
  2. The array of functions are invoked in series (as opposed to parallel).
  3. If an error (usually named err) is encountered at any operation in the flow array, it will short-circuit and immediately invoke the "complete"/"finish"/"done" callback.
  4. Arguments from the previously executed function are applied to the next function in the control flow, in order, and an "intermediate" callback is supplied as the last argument. Note: The first function only has this "intermediate" callback, and the "complete" callback will have the arguments of the last invoked function in the control flow (with consideration to any errors) but with an err argument prepended instead of an "intermediate" callback that is appended.
  5. The callbacks for each individual operation (I call this cbAsync in my examples) should be invoked when you're ready to move on: The first parameter will be an error, if any, and the second (third, fourth... etc.) parameter will be any data you want to pass to the subsequent operation.

The first goal is to get your code working almost verbatim alongside the introduction of async.waterfall. I decided to remove all your console.log statements and simplified your error handling. Here is the first iteration (untested code):

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

The second iteration divides up the operation flow a bit more. It puts it into smaller single-operation oriented chunks of code. I'm not going to comment it, it speaks for itself (again, untested):

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

The last iteration short-hands a lot of this with the use of some bind tricks to decrease the call stack and increase readability (IMO), also untested:

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);     }); } 
like image 21
zamnuts Avatar answered Sep 28 '22 00:09

zamnuts