...

суббота, 28 сентября 2013 г.

[Из песочницы] Chain.js: связываем синхронные и асинхронные функции в цепи

Chain.js — маленькая библиотека, сделанная для создания цепочек из синхронных и асинхронных функций. Идея цепочек родилась после знакомства с Common JS Promises. Само определение «обещаний» говорит, что promise — это значение выполнения одной операции. Если вам захотелось что-то изобрести, придумать или создать, то вы просто обязаны попытаться связать эти операции в цепочки. Конечно, вы не обязаны, и это естественно, но для меня это стало основным мотивом. Перед этим я действительно столкнулся с некоторыми неудобствами связывания promise-операций, хотя ожидал что именно с этим они мне и помогут.



Начало




Само существование Common JS Promises мне было известно давно. К взаимному проникновению меня толкнул Angular.js, который имеет свою реализацию promise/deferred. Немного помучившись с реализацией тех самых цепочек на основе promises, стало понятно, что promises для этого и не предназначены. Поэтому было принято решение изобрести велосипед пойти дальше и сделать необходимую функциональность самому.

К делу




В ходе размышлений о программном интерфейсе, Сhain.js обзавелась списком из 5 функций: 1 конструктор и 4 метода. Сжатая реализация имеет размер в 2.03КБ, полная (без комментариев ^_^) реализация имеет размер в 3.15КБ. Скачать эти файлы можно на странице сделанной специально для Chain.js. Прямых ссылок нет, боюсь, могу сломать.

Установка тривиальна, не отличается от других скриптов.

<script src="js/chain.dev.js" type="text/javascript"></script>




После этого библиотека доступна в глобальной области видимости (window, видимо) под именем Chain. Chain является конструктором новой цепи и имеет два основных варианта вызова (дополнительные варианты можете придумать сами):

var testChain = Chain();
// или
var anotherChain = new Chain;




Теперь мы можем ощутить всю мощь создавать наши цепи, для этого у нас есть 3 метода:


  • .then(Function | Array.<Function>)

  • .defer(Function | Array.<Function>)

  • .when(Chain | Array.<Chain>)




Метод .then принимает синхронные функции, .defer — асинхронные, .when — объекты Chain. Все 3 метода возвращают объект Chain, для которого вызываются (прямо как jQuery chaining). Также эти методы могут принимать либо один требуемый аргумент, либо массив таких аргументов. Для методов .then и .defer результат выполнения предыдущего звена передается следующему в указанную функцию первым аргументом.

Выполняется цепь только тогда, когда будет вызван метод .end, он же последний метод библиотеки.

Имеет такую сигнатурку:


  • .end([Function])




Функция будет вызвана при завершении всех операций в цепи. Указание функции необязательно.

Примеры для понимания:

var calculate = Chain();

calculate.
then(function() { // первая функция вызывается с аргументом undefined
return 0;
}).
then(function(result) { // не трудно догадаться, result равен 0
return result + 5;
}).
then(function(result) { // result равен 5
return result + 10;
}).
end(function(result) { console.log(result); }); // выведет 15




Иная запись с тем же результатом:

var calculate = new Chain;

function zero() { return 0; }
function plus5(num) { return num + 5; }
function plus10(num) { return num + 10; }
function log(result) { console.log(result); }

calculate.
then([zero, plus5, plus10]).
end(log); // выведет 15




Проделаем тоже самое асинхронно:

var calculate = Chain();

calculate.
defer(function(n, done) {
// первый аргумент будет равен undefined,
// второй - функция, которую надо вызвать при завершении нашей операции
done(0);
}).
defer(function(result, done) { // result равен 0
// та самая асинхронность
setTimeout(function() {
done(result + 5);
}, 1000);
}).
defer(function(result, done) { // result равен 5
done(result + 10);
}).
end(function(result) { console.log(result); }); // выведет 15




Тоже самое, по-другому:

var calculate = new Chain;

function zero(n, done) { done(0); }
function plus5(num, done) {
setTimeout(function() {
done(num + 5);
}, 1000);
}
function plus10(num, done) { done(num + 10); }
function log(result) { console.log(result); }

calculate.
defer([zero, plus5, plus10]).
end(log); // выведет 15




Ну и наконец, метод .when служит для соединения остальных ваших цепей. После выполнения метода .when в следующее звено передается массив с результатом выполнения предыдущих звеньев-цепей. Пример:

var five = Chain(), ten = Chain();

five.defer(function(n, done) {
setTimeout(function() {
done(5);
}, 1000);
});

ten.
when(five).
then(function(results) { return results[0] + 5; }); // results будет равен [5]

Chain().
when([ten, five, ten]).
end(function(results) { console.log(results); }); // выведет [10, 5, 10]


Особенности работы




Цепи переданные в метод .when выполняются параллельно. Последовательность в результирующем массиве будет соответствовать последовательности соединения(добавления) указанных цепей, см. последний пример.

Все функции переданные в метод .end после начала выполнения цепи будут ждать окончания выполнения цепи, т. е. цепи не будут запущены дважды. Вставьте console.log в каждую функцию в последнем примере, станет понятней.


Результат выполнения цепи сохраняется и будет возвращаться при каждом вызове метода .end, цепь запущена не будет. На самом деле, метод .end может принимать второй аргумент, заставляющий запустить цепь еще раз, но это поведение не доведено до ума. Если Chain.js придется вам по душе, то беру на себя обязательство с этим поведением разобраться.

Также, не реализовано прерывание выполнения цепи, хотя это возможно и легко осуществимо.


Конец




А конец ли? Приветствую ваши комментарии, предложения, пожелания. Спасибо за внимание.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий