Javascript 在 node.js 中使用异步瀑布
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25705067/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Async waterfall in node.js
提问by Alex Brodov
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..
我有 2 个异步运行的函数。我想使用瀑布模型编写它们。问题是,我不知道怎么...
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 的新手
回答by Volune
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) } }
读取文件
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) } }
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')
回答by zamnuts
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:
现在,关于瀑布控制流有几个关键部分:
- 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.
- The array of functions are invoked in series(as opposed to parallel).
- 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. - Arguments from the previously executed function are appliedto 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
errargument prepended instead of an "intermediate" callback that is appended. - The callbacks for each individual operation (I call this
cbAsyncin 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.
- 控制流由用于调用的函数数组指定为第一个参数,并在流完成时作为第二个参数指定一个“完成”回调。
- 函数数组是串行调用的(而不是并行调用)。
- 如果
err在流数组中的任何操作中遇到错误(通常命名为),它将短路并立即调用 "complete"/"finish"/"done"callback。 - 来自先前执行的函数的参数按顺序应用于控制流中的下一个函数,并提供“中间”回调作为最后一个参数。注意:第一个函数只有这个“中间”回调,“完整”回调将具有控制流中最后一个调用函数的参数(考虑到任何错误),但
err前面有一个参数而不是“中间”附加的回调。 cbAsync当你准备好继续时,应该调用每个单独操作的回调(我在我的例子中称之为):第一个参数将是一个错误,如果有的话,第二个(第三个,第四个......等)参数将是您要传递给后续操作的任何数据。
The first goal is to get your code working almost verbatim alongside the introduction of async.waterfall. I decided to remove all your console.logstatements and simplified your error handling. Here is the first iteration (untested code):
第一个目标是让您的代码在引入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.');
}
});
}
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 bindtricks 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);
});
}
回答by MastErAldo
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. 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.
基本上,需要一些时间来执行的 nodejs(以及更普遍的 javascript)函数(无论是用于 I/O 还是 CPU 处理)通常是异步的,因此事件循环(为了简单起见是一个不断检查要执行的任务的循环) ) 可以调用第一个函数正下方的函数,而不会因响应而被阻止。如果您熟悉其他语言,如 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:
因此,要使 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 is the return value from asyncTask1 and result2 is the return value for asyncTask2. You can this way nest how many asynchronous functions you want.
result1 是 asyncTask1 的返回值,result2 是 asyncTask2 的返回值。您可以通过这种方式嵌套所需的异步函数数量。
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");

