在node.js中使用Async瀑布

时间:2014-09-06 21:46:51

标签: javascript node.js asynchronous npm

我有两个我异步运行的功能。我想用瀑布模型来编写它们。问题是,我不知道如何......

这是我的代码:

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的新手

3 个答案:

答案 0 :(得分:56)

首先确定步骤并将它们写为异步函数(采用回调参数)

  • 阅读文件

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

请注意,我没有在这里进行特定的错误处理,我将利用async.waterfall来集中错误处理在同一个地方。

另外要注意,如果你在异步函数中有(if / else / switch / ...)分支,它总是调用回调一次(并且只有一次)。

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

我保留了函数名称,因为它有助于提高可读性,并有助于使用chrome debugger等工具进行调试。

如果您使用underscoreon npm),则还可以将第一个函数替换为_.partial(fs.readFile, 'stocktest.json')

答案 1 :(得分:14)

首先,请确保read the documentation regarding async.waterfall

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

  1. 控制流由作为第一个参数的调用函数数组指定,并且"完成"当流程结束时作为第二个参数回调。
  2. 系列中调用函数数组(而不是并行)。
  3. 如果在流数组中的任何操作中遇到错误(通常名为err),它将短路并立即调用"完成" /"完成&#34 ; /"完成" callback
  4. 来自先前执行的函数的参数是applied到控制流中的下一个函数,依次是"中间"回调作为最后一个参数提供。注意:第一个函数只有这个"中间"回调,"完成"回调将具有控制流中最后一个被调用函数的参数(考虑到任何错误),但前缀为err,而不是"中间"附加的回调。
  5. 当您准备好继续前进时,应调用每个单独操作的回调(我在示例中称之为cbAsync):第一个参数将是错误,如果有的话,第二个参数将是错误(第三,第四......等)参数将是您要传递给后续操作的任何数据。
  6. 第一个目标是让您的代码几乎逐字逐句地与async.waterfall的引入一起工作。我决定删除所有console.log语句并简化错误处理。这是第一次迭代(未经测试的代码):

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

    第二次迭代将操作流程再划分。它将它放入较小的单操作导向代码块中。我不会发表评论,它会说明问题(再次,未经测试):

    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技巧来减少调用堆栈并提高可读性(IMO),并且还未经测试,以此来解决很多问题:

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

答案 2 :(得分:2)

基本上,需要一些时间来执行的nodejs(以及更常见的javascript)函数(无论是用于I / O还是cpu处理)通常都是异步的,因此事件循环(简化它是一个不断检查任务的循环)要执行)可以调用第一个下面的函数,而不会被阻止响应。如果您熟悉其他语言(如C或Java),您可以将异步函数视为在另一个线程上运行的函数(它在javascript中不一定正确,但程序员不应该关心它)当执行终止时,该线程通知主要的一个(事件循环一)该作业已完成,并且它具有结果。

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

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


asyncFunction1(someparams, callback);

asyncFunction2(someotherparams);

执行流将调用:asyncFunction1,asyncFunction2和下面的每个函数,直到asyncFunction1结束,然后调用作为最后一个参数传递给asyncFunction1的回调函数,如果没有错误发生,则对数据执行某些操作。

因此,要使2个或更多异步函数只在它们结束时一个接一个地执行,你必须在它们的回调函数中调用它们:

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的返回值。您可以通过这种方式嵌套您想要的异步函数。

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

console.log("File successfully written");