简体   繁体   English

在node.js中使用Async瀑布

[英]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 请给我写一些例子,因为我是node.js的新手

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) 处理文件(我删除了示例中的大部分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) } } 

Note that I did no specific error handling here, I'll take benefit of async.waterfall to centralize error handling at the same place. 请注意,我没有在这里进行特定的错误处理,我将利用async.waterfall来集中在同一个地方的错误处理。

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

Plug everything with async.waterfall 使用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. 我留下了函数名称,因为它有助于提高可读性,并有助于使用chrome调试器等工具进行调试。

If you use underscore ( on npm ), you can also replace the first function with _.partial(fs.readFile, 'stocktest.json') 如果你使用下划线在npm上 ),你也可以用_.partial(fs.readFile, 'stocktest.json')替换第一个函数_.partial(fs.readFile, 'stocktest.json')

First and foremost, make sure you read the documentation regarding async.waterfall . 首先,请务必阅读有关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 . 如果在流数组中的任何操作中遇到错误(通常命名为err ),它将短路并立即调用“完成”/“完成”/“完成” 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. 注意:第一个函数只有这个“中间”回调,“完整”回调将包含控制流中最后一个被调用函数的参数(考虑到任何错误),但前面有一个err参数而不是“中间” “追加的回调。
  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. 当你准备继续前进时,应该调用每个单独操作的回调(我在我的例子中称之为cbAsync ):第一个参数将是错误(如果有的话),第二个参数(第三个,第四个......等等)。 )参数将是您要传递给后续操作的任何数据。

The first goal is to get your code working almost verbatim alongside the introduction of async.waterfall . 第一个目标是让您的代码几乎逐字地与async.waterfall的引入一起工作。 I decided to remove all your console.log statements and simplified your error handling. 我决定删除所有的console.log语句并简化错误处理。 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: 最后一次迭代通过使用一些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);
    });
}

Basically nodejs (and more generally javascript) functions that require some time to execute (be it for I/O or cpu processing) are typically asynchronous, so the event loop (to make it simple is a loop that continuously checks for tasks to be executed) can invoke the function right below the first one, without getting blocked for a response. 基本上,需要一些时间来执行的nodejs(以及更普遍的javascript)函数(无论是用于I / O还是cpu处理)通常都是异步的,因此事件循环(简化它是一个循环,不断检查要执行的任务) )可以在第一个函数下方调用函数,而不会被阻止响应。 If you are familiar with other languages like C or Java, you can think an asynchronous function as a function that runs on another thread (it's not necessarily true in javascript, but the programmer shouldn't care about it) and when the execution terminates this thread notifies the main one (the event loop one) that the job is done and it has the results. 如果您熟悉其他语言(如C或Java),您可以将异步函数视为在另一个线程上运行的函数(在javascript中不一定正确,但程序员不应该关心它)并且当执行终止时线程通知主要的一个(事件循环一)作业完成并且它有结果。

As said once the first function has ended its job it must be able to notify that its job is finished and it does so invoking the callback function you pass to it. 如上所述,一旦第一个函数结束了它的工作,它必须能够通知它的工作已经完成,并且它会调用你传递给它的回调函数。 to make an example: 举个例子:

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


asyncFunction1(someparams, callback);

asyncFunction2(someotherparams);

the execution flow would call: asyncFunction1, asyncFunction2 and every function below until asyncFunction1 ends, then the callback function which is passed as the last parameter to asyncFunction1 is called to do something with data if no errors occurred. 执行流程将调用:asyncFunction1,asyncFunction2和下面的每个函数,直到asyncFunction1结束,然后调用作为最后一个参数传递给asyncFunction1的回调函数,以便在没有错误发生时对数据执行某些操作。

So, to make 2 or more asynchronous functions execute one after another only when they ended you have to call them inside their callback functions: 因此,要使两个或更多异步函数只在它们结束时一个接一个地执行,您必须在它们的回调函数中调用它们:

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 is the return value from asyncTask1 and result2 is the return value for asyncTask2. result1是asyncTask1的返回值,result2是asyncTask2的返回值。 You can this way nest how many asynchronous functions you want. 您可以通过这种方式嵌套您想要的异步函数数量。

In your case if you want another function to be called after updateJson() you must call it after this line: 在您的情况下,如果您想在updateJson()之后调用另一个函数,则必须在此行之后调用它:

console.log("File successfully written");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM