Асинхронное управление потоками при помощи async

Код контроллера для некоторых страниц библиотеки будет зависеть от результатов многих асинхронных запросов, которые должны выполняться в определенном порядке или параллельно. Для того, чтобы управлять потоком выполнения, и выводить страницы, когда получена вся необходимая информация, будет использован async - известный модуль node.

Note:  В JavaScript существует много других способов управления аснхронным поведением и потоком выполнения, включая такой относительно  новый элемент языка JacaScript как Promises (обещания, промисы).

Модуль Async имеет массу полезных методов (см. документациюt the documentation). Вот некоторые наиболее важные функции:

  • async.parallel() для осуществеления любых операций, которые должны выполняться параллельно.
  • async.series() если нужно иметь уверенность, что асинхронные операции выполняются последовательно.
  • async.waterfall() для операций, которые должны выполняться последовательно, причем каждая операция зависит от результатов предыдущих операций.

Почему это необходимо?

Большинство методов, которые используются в  Express - асинхронные - вы определяете выполняемую операцию, передавая  callback-функцию. Метод завершается немедленно, а  callback-функция вызывается тогда, когда завершилась запрошенная операция. По соглашению, принятому в Express, callback-функция передает значение ошибки error  как первый параметр (или null при успехе) и результат функции (если есть) как второй параметр.

Если контроллер должен выполнить только одну асинхронную операцию, чтобы получить информацию для представления страницы, то реализация проста - мы просто представляем шаблон в колбэке. Фрагмент кода (ниже) демонстрирует это для функции, которая подсчитывает количество элементов модкли SomeModel (применяя метод Mongoose count() ):

exports.some_model_count = function(req, res, next) {
 
  SomeModel.count({ a_model_field: 'match_value' }, function (err, count) {
    // ... сделать что-то, если ошибка

    // При успехе представить результат, передав count в render-функцию (здесь - как переменную 'data').
    res.render('the_template', { data: count } );
  });
}

Однако что, если требуется сделать множественные асинхронные запросы, и результат нельзя представить, пока не завершились все операции? Наивная реализация могла бы использовать "венок" запросов, запуская последующие запросы в колбэках предыдущих, и представляя ответ в последнем колбэке. Проблема такого подхода состоит в том, что запросы должны вапольняться последовательно, хотя, вероятно, было бы более эффективно выполнять их параллельно. Это также может привести к усложненному вложенному коду, что обычно называют адом обратных вызовов ( callback hell ).

Намного лучше было бы выполнять все запросы параллельно, и иметь единственную callback-функцию, которая будет вызвана после того как все запросы выполнены. Именно такое выполнение операций модуль Async делает легким и простым!

Параллельные асинхронные операции

Метод async.parallel() используется для параллельного выполнения нескольких асинхронных операций.

Первый аргумент в async.parallel() - это коллекция асинхронных функций, которые требуется выполнить (массив, объект или другой итерируемый элемент). Каждая функция получает callback-функцию callback(err, result) , которую она должна вызвать при завершении, с ошибкой err (может быть null) и, возможно, со значением результата results.

Возможный второй аргумент для  async.parallel() - это callback -функция, которая должна быть вызвана после завершения всех функций, указанных в первом аргументе. Эта функция вызывается с аргументом ошибки и результатом - коллекцией результатов отдельных асинхронных операций. Тип коллекции - такой же, как и тип первого аргумента async.parallel (т.е. если передается массив асинхронных функций, итоговая callback-функция будет вызвана с массивом результатов). Если из параллельных функций сообщила об ошибке, сразу вызывается итоговая callback-функция, которая возвращает ошибку.

Пример ниже показывает, как это работает в случае, когда первый аргумент является объектом. Как видно, результаты возвращаются в объекте с такими же именами свойств, как у переданных функций.

async.parallel({ 
  one: function(callback) { ... },
  two: function(callback) { ... },
  ...
  something_else: function(callback) { ... }
  }, 
  // optional callback
  function(err, results) {
    // 'results' равны: {one: 1, two: 2, ..., something_else: some_value}
  }
);

Если вместо объекта передать массив функций как первый аргумент, результатом будет массив (порядок результатов в массиве такой же, как и порядок функций в массиве, а не порядок выполнения функций).

Последовательные асинхронные операции

Для выполнения нескольких асинхронных операций последовательно используется метод async.series() , при этом последующие функции не зависят от результатов предыдущих функций. Метод определяется и ведет себя так же, как и async.parallel().

async.series({ 
  one: function(callback) { ... },
  two: function(callback) { ... },
  ...
  something_else: function(callback) { ... }
  }, 
  // optional callback after the last asynchronous function completes.
  function(err, results) {
    // 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} 
  }
);

Заметка: Спецификация языка ECMAScript (JavaScript) устанавливает, что порядок  в перечислении объектов не определен, поэтому возможно, что функции не будут вызываться в том порядке, в котором вы их задали на всех платформах. Если порядок вызова действительно важен, вместо объекта следует передавать массив, как показано ниже.

async.series([
  function(callback) {
    // do some stuff ...
    callback(null, 'one'); 
  },
  function(callback) {
    // do some more stuff ... 
    callback(null, 'two'); 
  } 
 ], 
  // optional callback
  function(err, results) {
  // results is now equal to ['one', 'two'] 
  }
);

Последовательные зависимые асинхронные операции

Выполнение нескольких асинхронных операций последовательно, когда каждая операция зависит от результатов предыдущих операций, осуществляется методом async.waterfall().

Функции-callback, которая вызываются асинхронными функциями , содержит null как первый аргумент, и результаты в следующих аргументах. Каждая функция в последовательности (кроме первой) как аргументы использует результаты предыдущих функция, а callback-функция является последним аргументом. Когда  операции завершаются, вызывается финальная callback-функция, аргументы которой - объект err и результат последней операции. Как это работает, станет более ясным после рассмотрения примера - фрагмента кода, приведенного ниже ( пример взят из документации async):

async.waterfall([
  function(callback) {//первая функция в цепочке
    callback(null, 'one', 'two');//результаты 'one' и 'two' 
  }, 
  function(arg1, arg2, callback) {//вторая функция в цепочке 
    // arg1 равен 'one' , arg2 равен  'two' 
    callback(null, 'three'); //результат 'three'
  }, 
  function(arg1, callback) {
    // arg1 равен 'three'
    callback(null, 'done'); //результат 'done'
  }
], function (err, result) {
  // result равен 'done'
}
);

Установка async

Установим модуль async при помощи менеджера пакетов NPM, чтобы использовать его в своем коде. Это делается обычным способом - откроем окно команд в корне проекта LocalLibrary и введем команду:

npm install async

Дальнейшие шаги

Метки документа и участники

Метки: 
Внесли вклад в эту страницу: mdnwebdocs-bot, alxatr
Обновлялась последний раз: mdnwebdocs-bot,