- PVSM.RU - https://www.pvsm.ru -
Каждый программист, начинающий разрабатывать под Node.js, встаёт перед выбором стратегии организации асинхронного кода в проекте. В то время, как в небольших системных утилитах поддерживать гигиену асинхронного кода достаточно просто, при росте массы кода в проекте решение этой задачи начинает требовать введения дополнительного, так называемого control flow средства.
В этой статье будет рассмотрена небольшая control flow библиотека «Flowy» [1], являющаяся развитием идей проекта Step [2] Тима Касвелла, и ядро которой базируется на концепциях CommonJS Promises [3], а также приведены аргументы, почему же Promises — это так неудобно.
function leaveMessage(username, text, callback) {
Flowy(
function() {
// concurrent execution of two queries
model.users.findOne(username, this.slot());
model.settings.findOne(username, this.slot());
},
function(err, user, settings) {
// error propagating
if (!user) throw new Error('user not found');
if (!settings.canReceiveMessages) throw new Error('violating privacy settings');
model.messages.create(user, text, this.slot());
},
function(err, message) {
model.notifications.create(message, this.slot());
},
callback //any error will be automatically propagated to this point
);
}
Flowy
выполняется в контексте библиотеки (переменная this
). При этом контекст предоставляет возможность передавать данные на следующий шаг путем генерирования колбэков, которые можно передать классическим nodejs-like функциям в качестве последнего аргумента (вызов this.slot()
).this.slot()
завершатся успешно, либо же первый из них получит сообщение об ошибке.Программисту, начинающему знакомство с API неблокирующей подсистемы ввода-вывода Node.js, предлагается интерфейс асинхронных вызовов следующего вида:
fs.readFile('/etc/passwd', 'utf8', function (err, data) {
if (err) throw err;
console.log(data);
});
При использовании чужих модулей естесственным желанием было бы иметь интерфейс, схожий с описанным выше — правило наименьшего удивления [4] является одним из залогов поддерживаемого и легкоотлаживаемого кода. Отсюда появляется первое требование к библиотеке:
Мы хотим сохранить «родные» nodejs-like интерфейсы функций и колбэков. Каждый шаг Flowy
имеет интерфейс nodejs-колбэка, что позволяет легко оборачивать всю цепочку шагов в традиционную nodejs-функцию.
При этом, основной идеей Promises (в качестве примера реализации в дальнейшем будет использоваться библиотека «Q» [5] Криса Коуэла) является замена передачи колбэка последним аргументом в асинхронный вызов созданием цепочки вызовов методов Promise:
// chaining promises: Q.fcall(step1).then(step2).then(step3).done()
return getUsername()
.then(function (username) {
return getUser(username)
.then(function (user) {
// if we get here without an error, the value returned here
// or the exception thrown here resolves the promise returned by the first line
})
})
Первое, что бросается в глаза: функции возвращают Promise. Таким образом, для использования библиотеки необходимо все «классические» функции обернуть в Promise-адаптер (подробнее этот процесс описан на станице проекта), либо же разрабатывать код с жестко ориентированными на библиотеку интерфейсами (но при этом все публичные интерфейсы модуля необходимо будет обратно привести в классический вид, учитывая требование, сформулированное выше). Это неудобно. Это звучит пугающе и не менее пугающе выглядит. При этом сразу же на ум приходит второе требование к control flow библиотеке:
Библиотека должна быть лишь «клеем» между существующими частями системы и не становиться тяжелой зависимостью. Все особенности функционирования «Flowy» скрыты внутри шагов — того самого клея, — что позволяет функциям, использующим ее, оставаться «чистыми» для внешнего мира. Сор должен оставаться в избе.
При работе с библиотеками, позволяющими создавать цепочки (chaining) из асинхронных вызовов, часто возникает необходимость выполнить часть вызовов параллельно. Библиотека «Q» предоставляет следующее неловкое решение:
Q.allResolved(promises)
.then(function (promises) {
promises.forEach(function (promise) {
if (promise.isFulfilled()) {
var value = promise.valueOf();
} else {
var exception = promise.valueOf().exception;
}
})
})
В добавок ко всему, если мы вдруг захотим нарушить правило «один аргумент — одно возвращенное значение», то придется заниматься дополнительными упражнениями:
return getUsername()
.then(function (username) {
return [username, getUser(username)];
})
.spread(function (username, user) {
})
Читая этот код, само собой напрашивается еще одно требование к библиотеке:
Мы хотим легко выполнять несколько параллельных запросов и передавать любое количество аргументов в колбэки. «Flowy» это умеет без каких-либо дополнительных усилий со стороны разработчика благодаря своей архитектуре.
Итак, «Flowy» — это легковесная библиотека по управлению асинхронным потоком выполнения программы, позволяющая легко решать повседневные вопросы разработчиков под Node.js и хорошо зарекомендовавшая себя в production-окружении.
Данная статья демонстрирует лишь базовые возможности «Flowy». Для более подробного ознакомления, приглашаю всех посетить страничку проекта на гитхабе, где вы найдете обильную документацию со множеством примеров.
Полезные источники:
Автор: geeqie
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/25258
Ссылки в тексте:
[1] «Flowy»: https://github.com/geeqie/node-flowy
[2] Step: https://github.com/creationix/step
[3] CommonJS Promises: http://wiki.commonjs.org/wiki/Promises
[4] правило наименьшего удивления: http://en.wikipedia.org/wiki/Principle_of_least_astonishment
[5] «Q»: https://github.com/kriskowal
[6] http://www.commonjs.org: http://www.commonjs.org
[7] https://github.com/kriskowal/q: https://github.com/kriskowal/q
[8] http://habrahabr.ru/post/111634: http://habrahabr.ru/post/111634
[9] Источник: http://habrahabr.ru/post/166419/
Нажмите здесь для печати.