簡體   English   中英

在node.js中使用Async瀑布

[英]Using Async waterfall in node.js

我有兩個函數,我正在異步運行。 我想用瀑布模型來編寫它們。 問題是,我不知道怎么做..

這是我的代碼:

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 / ...)分支,它總是調用回調一次(並且只有一次)。

使用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調試器等工具進行調試。

如果你使用下划線在npm上 ),你也可以用_.partial(fs.readFile, 'stocktest.json')替換第一個函數_.partial(fs.readFile, 'stocktest.json')

首先,請務必閱讀有關async.waterfall的文檔

現在,關於瀑布控制流程有幾個關鍵部分:

  1. 控制流由作為第一個參數的調用函數數組指定,當流完成作為第二個參數時指定“完整”回調。
  2. 函數數組是串行調用的(而不是並行調用)。
  3. 如果在流數組中的任何操作中遇到錯誤(通常命名為err ),它將短路並立即調用“完成”/“完成”/“完成” callback
  4. 來自先前執行的函數的參數按順序應用於控制流中的下一個函數,並且提供“中間”回調作為最后一個參數。 注意:第一個函數只有這個“中間”回調,“完整”回調將包含控制流中最后一個被調用函數的參數(考慮到任何錯誤),但前面有一個err參數而不是“中間” “追加的回調。
  5. 當你准備繼續前進時,應該調用每個單獨操作的回調(我在我的例子中稱之為cbAsync ):第一個參數將是錯誤(如果有的話),第二個參數(第三個,第四個......等等)。 )參數將是您要傳遞給后續操作的任何數據。

第一個目標是讓您的代碼幾乎逐字地與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);
    });
}

基本上,需要一些時間來執行的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的回調函數,以便在沒有錯誤發生時對數據執行某些操作。

因此,要使兩個或更多異步函數只在它們結束時一個接一個地執行,您必須在它們的回調函數中調用它們:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM