Подтвердить что ты не робот

Использование Async водопада в node.js

У меня есть 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

4b9b3361

Ответ 1

Сначала определите шаги и запишите их как асинхронные функции (используя аргумент обратного вызова)

  • прочитайте файл

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

Я оставил имена функций, потому что это помогает читать и помогает отлаживать такие инструменты, как хром-отладчик.

Если вы используете underscore (для npm), вы также можете заменить первую функцию на _.partial(fs.readFile, 'stocktest.json')

Ответ 2

Прежде всего, убедитесь, что прочитайте документацию, касающуюся async.waterfall.

Теперь есть пара ключевых частей об управлении потоком водопада:

  • Управляющий поток задается массивом функций для вызова в качестве первого аргумента и "полным" обратным вызовом, когда поток завершен как второй аргумент.
  • Массив функций вызывается последовательно (в отличие от параллельного).
  • Если ошибка (обычно называемая err) встречается при любой операции в массиве потоков, она будет короткозамкнута и немедленно вызовет "завершение" / "завершение" / "сделано" callback.
  • Аргументы от ранее выполненной функции применяются к следующей функции в потоке управления, по порядку, и в качестве последнего аргумента предоставляется "промежуточный" обратный вызов. Примечание. Первая функция имеет этот "промежуточный" обратный вызов, а "полный" обратный вызов будет иметь аргументы последней вызываемой функции в потоке управления (с учетом любых ошибок), но с аргументом err, добавленным вместо "промежуточный" обратный вызов, который прилагается.
  • Обратные вызовы для каждой отдельной операции (я называю это 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);
    });
}

Ответ 3

В основном функции nodejs (и в целом javascript), требующие некоторого времени для выполнения (будь то для обработки ввода-вывода или процессора), обычно являются асинхронными, поэтому цикл событий (чтобы сделать его простым - это цикл, который непрерывно проверяет задачи для выполнения) может вызвать функцию, расположенную ниже первой, без блокировки для ответа. Если вы знакомы с другими языками, такими как 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");