在node.js中使用Async瀑布

我有2个function,我正在asynchronous运行。 我想用瀑布模型来写。 事情是,我不知道如何..

这是我的代码:

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 

任何想法如何使用瀑布写它,所以我将能够控制这个? 请给我写一些例子,因为我是node.js的新手

首先确定这些步骤,并将它们编写为asynchronous函数(带callback参数)

  • 阅读文件

     function readFile(readFileCallback) { fs.readFile('stocktest.json', function (error, file) { if (error) { readFileCallback(error); } else { readFileCallback(null, file); } }); } 
  • 处理文件(我在示例中删除了大部分console.log)

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

请注意,我没有在这里做具体的error handling,我会利用async.waterfall集中在同一地点的error handling。

另外要注意的是,如果你在asynchronous函数中有(if / else / switch / …)分支,它总是调用callback一次(也是唯一的一次)。

用async.waterfall插入所有东西

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

干净的例子

前面的代码过于冗长,使解释更清晰。 这是一个完整的清理示例:

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

我留下的函数名称,因为它有助于可读性,并帮助debugging工具,如铬debugging器。

如果你使用下划线 ( 在npm上 ),你也可以用_.partial(fs.readFile, 'stocktest.json')replace第一个函数

首先,确保您阅读有关async.waterfall的文档 。

现在有几个关于瀑布控制stream程的关键部分:

  1. 控制stream由作为第一个参数的调用函数数组指定,当stream作为第二个参数完成时,将执行“complete”callback。
  2. 函数数组被串联调用(而不是并行)。
  3. 如果在stream数组中的任何操作中遇到错误(通常命名为err ),将会短路并立即调用“完成”/“完成”/“完成” callback
  4. 先前执行的函数的参数将按顺序应用于控制stream中的下一个函数,并将“中间”callback作为最后一个参数提供。 注意:第一个函数只有这个“intermediate”callback函数,而“complete”callback函数在控制stream中会有最后一个调用函数的参数(考虑到任何错误),但是前面加了err参数,而不是“intermediate “附加callback。
  5. 每个单独的操作(我在我的例子中称为cbAsync )的callback应该在你准备好继续时调用:第一个参数将是一个错误,如果有的话,第二个(第三,第四…等等)参数将是您想要传递给后续操作的任何数据。

第一个目标是在引入async.waterfall同时让你的代码几乎async.waterfall 。 我决定删除所有的console.log语句,并简化了error handling。 这是第一次迭代( 未经testing的代码 ):

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

第二次迭代将操作stream程分了一点。 它把它放到更小的面向单一操作的代码块中。 我不打算评论它,它自己说话( 再次,未经testing ):

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

最后一个迭代通过使用一些bind技巧来减less调用堆栈并提高可读性(IMO),这也是未经testing的:

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

基本上,需要一些时间来执行的nodejs(更常见的是javascript)函数通常是asynchronous的,所以事件循环(简单来说就是一个循环,不断检查要执行的任务)可以调用第一个函数下面的函数,而不会被阻止响应。 如果您熟悉C或Java等其他语言,则可以将asynchronous函数视为在另一个线程上运行的函数(在JavaScript中不一定是这样,但程序员不应该关心它),并且在执行结束时线程通知主要的一个(事件循环一)工作完成,并有结果。

正如前面所说的,第一个函数已经结束了它的工作,它必须能够通知它的工作已经完成,并且它会调用你传递给它的callback函数。 举个例子:

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

执行stream将调用:asyncFunction1,asyncFunction2和下面的每个函数,直到asyncFunction1结束,然后调用作为最后一个parameter passing给asyncFunction1的callback函数,在没有错误发生时调用数据。

所以,要使2个或更多的asynchronous函数一个接一个地执行,只有当它们结束时,你必须在它们的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是asyncTask1的返回值,result2是asyncTask2的返回值。 你可以通过这种方式嵌套你想要的asynchronousfunction。

在你的情况下,如果你想在updateJson()之后调用另一个函数,你必须在这行之后调用它:

 console.log("File successfully written");