Асинхронное управление потоками при помощи async
Код контроллера для некоторых страниц библиотеки будет зависеть от результатов многих асинхронных запросов, которые должны выполняться в определённом порядке или параллельно. Для того, чтобы управлять потоком выполнения, и выводить страницы, когда получена вся необходимая информация, будет использован async - известный модуль node.
Примечание: В JavaScript существует много других способов управления асинхронным поведением и потоком выполнения, включая такой относительно новый элемент языка JavaScript как промисы (promises).
Модуль Async имеет массу полезных методов (см. документацию the documentation). Вот некоторые наиболее важные функции:
async.parallel()
для осуществления любых операций, которые должны выполняться параллельно.async.series()
если нужно иметь уверенность, что асинхронные операции выполняются последовательно.async.waterfall()
для операций, которые должны выполняться последовательно, причём каждая операция зависит от результатов предыдущих операций.
Почему это необходимо?
Большинство методов, которые используются в Express - асинхронные - вы определяете выполняемую операцию, передавая колбэк-функцию. Метод завершается немедленно, а колбэк-функция вызывается тогда, когда завершилась запрошенная операция. По соглашению, принятому в Express, колбэк-функция передаёт значение ошибки 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 ).
Намного лучше было бы выполнять все запросы параллельно, и иметь единственную колбэк-функцию, которая будет вызвана после того как все запросы выполнены. Именно такое выполнение операций модуль Async делает лёгким и простым!
Параллельные асинхронные операции
Метод async.parallel()
используется для параллельного выполнения нескольких асинхронных операций.
Первый аргумент в async.parallel()
- это коллекция асинхронных функций, которые требуется выполнить (массив, объект или другой итерируемый элемент). Каждая функция получает колбэк-функцию callback(err, result)
, которую она должна вызвать при завершении, с ошибкой err
(может быть null
) и, возможно, со значением результата results
.
Возможный второй аргумент для async.parallel()
- это callback -функция, которая должна быть вызвана после завершения всех функций, указанных в первом аргументе. Эта функция вызывается с аргументом ошибки и результатом - коллекцией результатов отдельных асинхронных операций. Тип коллекции - такой же, как и тип первого аргумента async.parallel (т.е. если передаётся массив асинхронных функций, итоговая колбэк-функция будет вызвана с массивом результатов). Если любая из параллельных функций сообщила об ошибке, сразу вызывается итоговая колбэк-функция, которая возвращает ошибку.
Пример ниже показывает, как это работает в случае, когда первый аргумент является объектом. Как видно, результаты возвращаются в объекте с такими же именами свойств, как у переданных функций.
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
как первый аргумент, и результаты в следующих аргументах. Каждая функция в последовательности (кроме первой) как аргументы использует результаты предыдущих функция, а колбэк-функция является последним аргументом. Когда операции завершаются, вызывается финальная колбэк-функция, аргументы которой - объект 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: Основы шаблонов.