非同期に実行している2つの関数があります。ウォーターフォールモデルを使用して記述したいと思います。事は、私は方法がわからないということです。
ここに私のコードがあります:
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が初めてなので、例をいくつか書いてください
ファイルを読む
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/...)ブランチがある場合は、常に1回だけコールバックを呼び出すことに注意してください。
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デバッガーなどのツールでデバッグできるため、関数名を残しました。
アンダースコア ( npm )を使用する場合、最初の関数を_.partial(fs.readFile, 'stocktest.json')
に置き換えることもできます
何よりもまず、 async.waterfall
に関するドキュメントをお読みください であることを確認してください。
さて、ウォーターフォール制御フローにはいくつかの重要な部分があります。
err
という名前)がフロー配列のいずれかの操作で発生した場合、それは短絡し、すぐに "complete"/"finish"/"done" callback
を呼び出します。err
引数が付加されます「追加されるコールバック。cbAsync
を呼び出します)は、先に進む準備ができたら呼び出す必要があります。最初のパラメーターはエラーであり、2番目(3番目、4番目...など)になります。 )パラメータは、後続の操作に渡すデータです。最初の目標は、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.');
}
});
}
2番目の反復では、操作フローをもう少し分割します。それは、より小さな単一操作指向のコードの塊にそれを置きます。私はそれをコメントするつもりはありません、それはそれ自身のために話します(再び、テストされていない):
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);
});
}
基本的に、実行にある程度の時間を必要とする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");