Реактивные расширения для
JavaScript......и пусть события сами идут
Где мы используем события?
ВЕЗДЕ
Что не так с событиями?
● как компоновать?● как фильтровать?● как собирать данные от разных
событий?● как не сойти с ума от
асинхронности?● как не строить Пирамиды Обратных
Вызовов?● … (еще примерно 42 пункта)
О ужасный callback!
$('#some-element').animate({ width: 100, height: 50}, { duration: 300, complete: function() { // Step 2: $('#another-element').fadeOut(300, function() { // Step 3: setTimeout(function() { // Step 4: $.ajax({ url: 'http://google.com', }).done(function() { // Step 5: setTimeout(function() { // Step 6: $('#third-element').remove(); }, 250); }); }, 300); }); }});
О ужасный callback!
step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); });});
О ужасный callback! (и доступ к данным)
step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) {
if (value2 === 4) return null step4(value3, function(value4) { if (value2 > value3) work() }); }); });});
Варианты решения а-ля promise...Q.fcall(step1) .then(step2) .then(step3) .then(step4) .then(function (value4) { // Do something with value4 }) .done();
https://github.com/kriskowal/q/
A tool for making and composing asynchronous promises in JavaScript (282 362 forks at Github).
...или настоящими promise
function successFunc(){ console.log( “success!” ); }
function failureFunc(){ console.log( “failure!” ); }
$.when(
$.ajax("/main.php" ),
$.ajax("/modules.php" ),
$.ajax(“/lists.php” )
).then( successFunc, failureFunc );
Все равно не хватает контроля над данными!
Reactive Extensions for JS
...is a library to compose asynchronous and event-based programs using
observable collections and LINQ-style query operators.
Реактивные расширения
...библиотека для создания асинхронных и основанных на событиях программ используя
наблюдаемые коллекции и LINQ операторы.
Состав RxJS:
● объекты для представления асинхронных потоков данных
● операторы для создания, фильтрации и управления подобными объектами и данными внутри них
● немного магии
Реактивное программирование
- парадигма, ориентированная на потоки данных и распространение изменений.
Это означает, что должна существовать возможность легко выражать статические и динамические потоки данных, а также то, что выполняемая модель должна автоматически распространять изменения сквозь поток данных.
2 примера реактивности из жизни1. Excel 2. Ищем работу:
обзванивать вакансииpull vs
разместить резюме ждать звонков
push
Основа идеологии
НаблюдательObserver
Наблюдаемое (поток)
Observable
подписывается (subscribe) ⇒ ⇐ присылает сообщения (onNext)
Наблюдаемая последовательнсть
RxJS против не-RxJS
$
('button').clickAsObserva
ble()
.skip(2)
.take(2)
.timeInterval()
.doAction(flickerButton)
.subscribe(logClicks)
var i= 0, lastTs = new Date(), nowTs$('button').click(function(){ i++; if (i<=2 || i>=4) return nowTs = new Date() flickerButton() logClicks(diff(lastTs, nowTs))})
query
Используем привычные функции
Array● concat● every● filter● map● reduce● some● forEach
Observable● concat● all● where● select● aggregate● any● subscribe
Поддержка родных событий jQuery•bind - bindAsObservable
•delegate - delegateAsObservable
•live - liveAsObservable
•on - onAsObservable
•$.Deferred.toObservable /
Rx.Observable.toDeferred
Мышь и клавиатура
•change - changeAsObservable
•click - clickAsObservable
•dblclick - dblclickAsObservable
•focus - focusAsObservable
•focusin - focusinAsObservable
•focusout - focusoutAsObservable
•hover - hoverAsObservable
•keydown - keydownAsObservable
•keypress - keypressAsObservable
•keyup - keyupAsObservable
•load - loadAsObservable
•mousedown - mousedownAsObservable
•mouseenter - mouseenterAsObservable
•mouseleave - mouseleaveAsObservable
•mousemove - mousemoveAsObservable
•mouseout - mouseoutAsObservable
•mouseenter - mouseenterAsObservable
•mouseleave - mouseleaveAsObservable
•mousemove - mousemoveAsObservable
•mouseout - mouseoutAsObservable
Ajax и анимация
• ajax - ajaxAsObservable
• get - getAsObservable
• getJSON - getJSONAsObservable
• getScript - getScriptAsObservable
• post - postAsObservable
• animate - animateAsObservable
• fadeIn - fadeInAsObservable
• fadeOut - fadeOutAsObservable
• fadeTo - fadeToAsObservable
• fadeToggle - fadeToggleAsObservable
• hide - hideAsObservable
• show - showAsObservable
• slideDown - slideDownAsObservable
• slideToggle - slideToggleAsObservable
• slideUp - slideUpAsObservable
Состав RxJS:
● объекты для представления асинхронных потоков данных
● операторы для создания, фильтрации и управления подобными объектами и данными внутри них
● немного магии
Операторы объединения
● Amb● Merge● Concat● CombineLatest● Zip● Repeat
stream y
stream x
resultresult = fn(x, y, …, n)
Операторы фильтрации и выбора● where (filter) / whereTrue / whereFalse● take / takeUntil / takeWhile● skip / skipUntill / skipWhile● select (map) / selectMany /
selectProperty
stream x
resultresult = fn.apply(x)
Операторы для данных
● scan● count● min● max● groupBy● distinct / distinctUntilChanged● throttle
Временные диаграммы
Временные диаграммы вживую
j q ut
result = source.throttle(1500)
jqu
jqu
source
resultjqu
jque
jque
1,5 сек 1,5 сек
0,3 сек
.select()negativeStream = stream.select(function(value) { return -value });
negativeStream = stream.select(invert);function invert(value) { return -value });
.where()negativeStream = stream .where(function(value) { return value % 2 == 0 });
negativeStream = stream.where(isEven);
xStream.merge(yStream)
xStream.combineLatest(yStream)
x.zip(y)
.bufferWithTime(3000)
.bufferWithCount(3)
.bufferWithTimeOrCount(3000, 3)
dots.delay(1000)
Операторы - over 9000aggregateallambandanyasObservableaveragebufferbufferWithCountbufferWithTimebufferWithTimeOrCountcase | switchCasecatch | catchExceptioncatch | catchExceptioncombineLatestconcatconnectcontainscountcreatecreateWithDisposabledefaultIfEmptydeferdelaydelayWithSelectordematerializedistinctdistinctUntilChangeddo | doActiondoWhileelementAtelementAtOrDefaultempty
everyexpandfilterfinally | finallyActionfindfindIndexfirstfirstOrDefaultflatMapflatMapLatestfor | forInforkJoinfromArraygenerategenerateWithAbsoluteTimegenerateWithRelativeTimegroupBygroupByUntilgroupJoinif | ifThenignoreElementsintervalisEmptyjoinlastlastOrDefaultmanySelectmapmaxmaxBymergemergeObservablemin
minBymulticastneverobserveOnonErrorResumeNextonErrorResumeNextpluckpublishpublishLastpublishValuerangereducerefCountrepeatrepeatreplayretryreturn | returnValuesamplescanselectselectManyselectSwitchsinglesingleOrDefaultskipskipLastskipLastWithTimeskipUntilskipWhilesomestartstartWith
subscribesubscribeOnsumswitch | switchLatesttaketakeLasttakeLastBuffertakeLastBufferWithTimetakeLastWithTimetakeUntiltakeWhilethrottlethrottleWithSelectorthrow | throwExceptiontimeIntervaltimeouttimeoutWithSelectortimertimestamptoArraytoAsyncusingwhenwherewhile | whileDowindowwindowWithCountwindowWithTimewindowWithTimeOrCountzip
Языки с библиотеками RX
1.node.js2. Java3.Ruby4.Python5.ObjectiveC6.C++7. ...8.https://github.com/Reactive-Extensions
Демо
закрепим в jsFiddle полученные знания
Большое приложение
основанное на RxJS
selectedItem => {id: 1, title: “Матрешка”}loadPanelStatus => {status: true}pageSelection => {page: 4}
loadPanelStatus => {status: true}couponChanges => {id: undefined}itemRemoval => {id: 1}
Модуль корзины
Модуль каталога
КонтролерselectedItem {id: 1}
selectedItemloadPanelStatus
couponChangesitemRemoval
inputstreams
itemRemoval {id: 2}
Модуль корзины
Модуль каталога
Контролер
couponChanges .selectPropert(‘id’) => {id: “couponId”} => “couponId” .select(affectedItems) => “couponId” => [1,2,4] .subscribe(updateItemsPrice) itemRemoval .skipUntil(loadPanelStatus.whereTrue()) .takeUntil(loadPanelStatus.whereFalse()) .subscribe(markItemAsRemoved) <= {id: 1}
function affectedItems(coupon) { return $(‘.items.’+coupon).map(function(item){ return $(item).data(‘id’) }) }
selectedItem {id: 1}
selectedItemloadPanelStatus
couponChangesitemRemoval
inputstreams
itemRemoval {id: 2}
Вредные советы
● одноразовые потоки● потоки-сироты● превращаем все события в потоки,
все!● общие названия потоков (dataStream)● свои операторы не читая
документацию
Полезные советы
● простые данные в сообщениях● говорящие названия для потоков● простая логика для объединения● промежуточные потоки● функциональный подход
Фото кода
Литература:
1. http://www.introtorx.com/Content/v1.0.10621.0/00_Foreword.html
2. https://github.com/Reactive-Extensions
3. https://github.com/Reactive-Extensions/RxJS/tree/master/doc
4. http://blogs.microsoft.co.il/blogs/bnaya/archive/2010/02/25/rx-for-beginners-toc.aspx
5. http://www.slideshare.net/mattpodwysocki/cascadiajs-dont-cross-the-streams
6. http://www.slideshare.net/panesofglass/rx-workshop
7. http://www.infoq.com/reactive-extensions/
8. http://mywebbasedcomputer.com/users/johngslater/tech/rx/bubbleDiagrams.html
9. https://github.com/Netflix/RxJava/wiki/Observable-Utility-Operators
10. Matthew Podwysocki
Вопросы?