Асинхронное управление потоками при помощи 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
Дальнейшие шаги
- Вернуться учебнику Express, Part 5: Вывести данные библиотеки.
- Перейти к следующему разделу части 5: Основы шаблонов.