Rxjs практика

Brand New to RxJS?

Start getting familiar with all the key concepts needed to be productive with our RxJS Primer!


Content

Operators

Operators are the horse-power behind observables, providing an elegant, declarative solution to complex asynchronous tasks. This section contains all RxJS operators, included with clear, executable examples. Links to additional resources and recipes for each operator are also provided, when applicable.

Operator Categories

  • ​Combination​

  • ​Conditional​

  • ​Creation​

  • ​Error Handling​

  • ​Multicasting​

  • ​Filtering​

  • ​Transformation​

  • ​Utility​

OR…

​Complete listing in alphabetical order​

Understanding Subjects

A Subject is a special type of Observable which shares a single execution path among observers.

  • ​Overview​

  • ​AsyncSubject​

  • ​BehaviorSubject​

  • ​ReplaySubject​

  • ​Subject​

Concepts

Without a solid base knowledge of how Observables work behind the scenes, it’s easy for much of RxJS to feel like ‘magic’. This section helps solidify the major concepts needed to feel comfortable with reactive programming and Observables.

  • ​RxJS Primer​

  • ​Get started transforming streams with map, pluck, and mapTo​

  • ​Time based operators comparison​

  • ​RxJS v5 -> v6 Upgrade​

Recipes

Recipes for common use-cases and interesting solutions with RxJS.

  • ​Alphabet Invasion Game​

  • ​Battleship Game​

  • ​Breakout Game​

  • ​Car Racing Game​

  • ​Catch The Dot Game​

  • ​Click Ninja Game​

  • ​Flappy Bird Game​

  • ​Game Loop​


  • ​Horizontal Scroll Indicator​

  • ​HTTP Polling​

  • ​Lockscreen​

  • ​Matrix Digital Rain​

  • ​Memory Game​

  • ​Mine Sweeper Game​

  • ​Platform Jumper Game​

  • ​Progress Bar​

  • ​Save Indicator​

  • ​Smart Counter​

  • ​Stop Watch​

  • ​Space Invaders Game​

  • ​Swipe To Refresh​

  • ​Tank Battle Game​

  • ​Tetris Game​

  • ​Type Ahead​

  • ​Uncover Image Game​

Introductory Resources

New to RxJS and reactive programming? In addition to the content found on this site, these excellent resources will help jump start your learning experience!

Reading

  • ​RxJS Introduction — Official Docs

  • ​The Introduction to Reactive Programming You’ve Been Missing — André Staltz

  • ​RxJS: Observables, Observers and Operators Introduction — Todd Motto

Videos

  • ​Ultimate RxJS — Brian Troncone

  • ​Asynchronous Programming: The End of The Loop — Jafar Husain

  • ​What is RxJS? — Ben Lesh

  • ​Creating Observable from Scratch — Ben Lesh

  • ​Introduction to RxJS Marble Testing — Brian Troncone

  • ​Introduction to Reactive Programming — André Staltz

  • ​Reactive Programming using Observables — Jeremy Lund

Tools

  • ​Rx Marbles — Interactive diagrams of Rx Observables — André Staltz

  • ​Rx Visualizer — Animated playground for Rx Observables — Misha Moroshko

  • ​Reactive.how — Animated cards to learn Reactive Programming — Cédric Soulas

  • ​Rx Visualization — Visualizes programming with RxJS — Mojtaba Zarei


Interested in RxJS 4? Check outDenis Stoyanov’sexcellenteBook!

​简体中文​

На практике: изменяемость в JavaScript

Функциональное программирование в JavaScript хорошо развивается. Но по своей сущности JS — очень изменчивый язык, состоящий из множества парадигм. Ключевая особенность функционального программирования — неизменяемость. Другие функциональные языки выбросят ошибку, когда разработчик попытается изменить неизменяемый объект. Тогда как мы можем примирить врожденную изменяемость JS при написании функционального или функционального реактивного JS?

Когда мы говорим о функциональном программировании в JS, слово «неизменяемое» используется много, но разработчик обязан всегда держать ее в голове. Например, Redux полагается на одно неизменяемое дерево состояний. Однако сам JavaScript способен изменять объект состояния. Чтобы реализовать неизменяемое дерево состояний, нам нужно каждый раз при изменении состояния возвращать новый объект состояния.

Для неизменяемости объекты JavaScript также могут быть заморожены с помощью

Обратите внимание, что это «неглубокая» заморозка — значения объектов внутри замороженного объекта все еще могут быть изменены. Для гарантированной неизменяемости такие функции «глубокой» заморозки, как Mozilla deepFreeze() и npm deep-freeze могут рекурсивно замораживать объекты

Замораживание наиболее применимо в тестах, а не в приложении. Тесты будут оповещать разработчиков о возникновении изменений, чтобы их можно было исправить, и избежать загромождающего в основном коде.

Существуют также библиотеки, поддерживающие неизменяемость в JS. Mori предоставляет постоянные структуры данных на основе Clojure. Immutable.js от Facebook также предоставляет неизменяемые коллекции для JS. Библиотеки утилит, такие как Underscore.js и lodash, предоставляют методы и модули для более функционального стиля программирования (а стало быть направленного на неизменяемость).

Что такое функциональное реактивное программирование?

Более полное определение от родоначальника FRP Конала Эллиота звучит так: функциональное реактивное программирование — денотативное и продолжительное во времени. Эллиот предпочитает описывать эту парадигму программирования как денотативное программирование с продолжительностью во времени, а не «функциональное реактивное программирование».

Функциональное реактивное программирование, в его самом простом, оригинальном определении, имеет два фундаментальных свойства:

  • денотатив: значение каждой функции или типа является точным, простым и независимым от реализации (функциональная часть)
  • продолжительно во времени: : между любыми двумя точками есть бесконечное число других точек; обеспечивает гибкость трансформации, эффективность, модульность и точность (реактивная часть)

И еще раз: функциональное реактивное программирование — это программирование в декларативном стиле с изменяющимися во времени значениями.

Чтобы понять продолжительность во времени, рассмотрим аналогию с использованием векторной графики. Векторная графика имеет бесконечное разрешение. В отличие от растровой графики (дискретное разрешение) векторная графика масштабируется безгранично, она никогда не перерисовывается или становится нечеткой. «Выражения FRP описывают целые эволюции значений во времени, представляя эти эволюции непосредственно как значения первого класса», — Конал Эллиот.

Функциональное реактивное программирование должно быть:

  • динамическим: может реагировать во времени или на изменение входных данных
  • изменяющимся во времени: реактивное поведение может изменяться с продолжительностью во времени, а реактивные значения изменяются дискретно
  • эффективным: минимизировать объем обработки, необходимый при изменении входных данных
  • осведомленным о прошлом: чистые функции сопоставляют предыдущее значение состояния со следующим; изменения состояния относятся к локальному элементу, а не к глобальному состоянию программы

Здесь можно рассмотреть слайды Колала Эллиота о cущности и происхождении FRP. Язык программирования Haskell поддается истинному FRP благодаря своей функциональной, чистой и ленивой природе. Эван Кзаплиски, создатель Elm, дает большой обзор FRP в своем выступлении «Управление временем и пространством: понимание многих подходов FRP».

В самом деле, давайте коротко поговорим об Elm Эвана Кзаплиски. Elm — это функциональный, типизированный язык для создания веб-приложений. Он компилируется в JavaScript, CSS и HTML. Архитектура Elm послужила вдохновением для контейнера состояния Redux приложений JS. Первоначально Elm позиционировался истинным функционально реактивным языком программирования, но начиная с версии 0.17 он реализовывал подписки вместо сигналов в интересах облегчения изучения и использования языка. На этом Elm простился с FRP.

Observable

Observable — наблюдаемый объект. Нечто близкое к Iterable — итерируемый объект. Observable — объект, за которым можно наблюдать и как-то реагировать, если он меняется. Observable — это как множественный Promise.

# Single Multiple
Pull Function Iterator
Push Promise Observable

В примерах выше наблюдаемый объект создавался из элемента HTML документа и события . Но его можно создать, например, просто из массива, тогда вместо кликов будут обрабатываться элементы массива.

Создания Observable вручную

Наблюдаемый объект получает значения 1, 2, 3 сразу (синхронно) при подписке, и значение 4 через одну секунду.

Чтобы увидеть эти значения, необходимо вызвать Observable и подписаться на него.

Результат в консоле

Чтобы положить значение в Observable снаружи, то есть не внутри функции create надо использовать вспомогательный тип Subject и его метод .

Why use switchMap?

The main difference between and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.

This works perfectly for scenarios like typeaheads where you are no longer concerned with the response of the previous request when a new input arrives. This also is a safe option in situations where a long lived inner observable could cause memory leaks, for instance if you used mergeMap with an interval and forgot to properly dispose of inner subscriptions. Remember, maintains only one inner subscription at a time, this can be seen clearly in the .

Be careful though, you probably want to avoid in scenarios where every request needs to complete, think writes to a database. could cancel a request if the source emits quickly enough. In these scenarios mergeMap is the correct option.

​​​​

Getting Started

git clone https://github.com/Reactive-Extensions/rxjs.gitcd ./rxjs

«`bash` $ npm install rx $ npm install -g rx

$ bower install rxjs
$ jam install rx
$ Install-Package RxJS-All
<scriptsrc="rx.js"><script><scriptsrc="rx.all.js"><script><scriptsrc="rx.lite.js"><script>
<scriptsrc="rx.aggregates.js"><script><scriptsrc="rx.async.js"><script><scriptsrc="rx.backpressure.js"><script><scriptsrc="rx.binding.js"><script><scriptsrc="rx.coincidencejs"><script><scriptsrc="rx.experimental.js"><script><scriptsrc="rx.joinpatterns.js"><script><scriptsrc="rx.time.js"><script><scriptsrc="rx.virtualtime.js"><script><scriptsrc="rx.testing.js"><script>
require({'paths'{'rx''path/to/rx-lite.js'}},'rx',(Rx)=>{constobs=Rx.Observable.of(42);obs.forEach(x=>console.log(x));});

Why use shareReplay?

You generally want to use when you have side-effects or taxing computations that you do not wish to be executed amongst multiple subscribers. It may also be valuable in situations where you know you will have late subscribers to a stream that need access to previously emitted values. This ability to replay values on subscription is what differentiates and .

For instance, suppose you have an observable that emits the last visited url. In the first example we are going to use

const routeEnd =newSubject<{data any, url string}>();​const lastUrl = routeEnd.pipe(pluck('url'),share());​const initialSubscriber = lastUrl.subscribe(console.log);​routeEnd.next({data{}, url'my-path'});​const lateSubscriber = lastUrl.subscribe(console.log);

In the above example nothing is logged as the subscribes to the source. Now suppose instead we wanted to give access to the last emitted value on subscription, we can accomplish this with

import{ Subject }from'rxjs/Subject';import{ ReplaySubject }from'rxjs/ReplaySubject';import{ pluck, share, shareReplay, tap }from'rxjs/operators';​const routeEnd =newSubject<{data any, url string}>();​const lastUrl = routeEnd.pipe(tap(_ => console.log('executed')),pluck('url'),shareReplay(1));​const initialSubscriber = lastUrl.subscribe(console.log);​routeEnd.next({data{}, url'my-path'});​const lateSubscriber = lastUrl.subscribe(console.log);

Note that this is similar behavior to what you would see if you subscribed a to the stream, then subscribed to that

const routeEnd =newSubject<{data any, url string}>();​const shareWithReplay =newReplaySubject();​const lastUrl = routeEnd.pipe(pluck('url')).subscribe(val => shareWithReplay.next(val));​routeEnd.next({data{}, url'my-path'});​shareWithReplay.subscribe(console.log);

In fact, if we dig into the source code we can see a very similar technique is being used. When a subscription is made, will subscribe to the source, sending values through an internal

( )

returnfunctionshareReplayOperation(this Subscriber<T>, source Observable<T>){    refCount++;if(!subject || hasError){      hasError =false;      subject =newReplaySubject<T>(bufferSize, windowTime, scheduler);      subscription = source.subscribe({next(value){ subject.next(value);},error(err){          hasError =true;          subject.error(err);},complete(){          isComplete =true;          subject.complete();},});}​​const innerSub = subject.subscribe(this);​​return()=>{      refCount--;      innerSub.unsubscribe();if(subscription && refCount ===&& isComplete){        subscription.unsubscribe();}};};}

​​​​

Unsubscribe

Теперь попробуем реализовать более сложный пример. Напишем таймер, который будет отсчитывать секунды с момента подписки, и передавать их наблюдателям.

const timer = new Observable(observer => {// объявляем счетчик  let counter = 0; setInterval(() => {// передаем значение счетчика    // наблюдателю и увеличиваем его на единицуobserver.next(counter++); }, 1000);});// просто логируем каждое значениеtimer.subscribe({ next: console.log });

Код получился достаточно простой. Внутри функции-подписки мы объявляем переменную счетчик(counter). Затем, используя замыкание, получаем доступ к переменной из стрелочной функции в setInterval. И каждую секунду передаем переменную наблюдателю, после чего инкрементируем ее. Дальше подписываемся на поток, указываем только один метод — next. Не стоит переживать, что другие методы мы не объявили. Ни один из методов наблюдателя не является обязательным. Мы даже можем передать пустой объект, но в этом случае поток будет работать впустую.

После запуска мы увидим заветные логи, которые будут появляться каждую секунду. Если хотите, можете поэкспериментировать и подписаться на поток несколько раз. Вы увидите, что каждый из потоков будет выполняться независимо от остальных.

Если подумать, то наш поток будет выполняться в течение жизни всего приложения, ведь никакой логики отмены setInterval у нас нет, а в функции-подписке нет вызова метода complete. Но что, если нам нужно, чтобы поток завершился?

На самом деле все очень просто. Если посмотреть в документацию, то можно увидеть, что метод subscribe возвращает объект подписки. У данного объекта есть метод unsubscribe. Вызовем его, и наш наблюдатель перестанет получать значения из потока.

const subscription = timer.subscribe({ next: console.log });// поток завершится через 5 секундsetTimeout(() => subscription.unsubscribe(), 5000);

После запуска мы увидим, что счетчик остановится на цифре 4. Но, хоть мы и отписались от потока, наша функция setInterval продолжает работать. Она каждую секунду инкрементирует наш счетчик и передает его наблюдателю-пустышке. Чтобы такого не происходило, надо написать логику отмены интервала. Для этого нужно вернуть из функции-подписки новую функцию, в которой будет реализована логика отмены.

const timer = new Observable(observer => {  let counter = 0;  const intervalId = setInterval(() => {    observer.next(counter++);  }, 1000);  return () => {   clearInterval(intervalId);  }});

Теперь мы можем вздохнуть с облегчением. После вызова метода unsubscribe произойдет вызов нашей функции отписки, которая очистит интервал.

На практике: функциональное реактивное программирование на JavaScript

Традиционное определение FRP может быть трудным для понимания, особенно для разработчиков, не имеющих опыта работы с такими языками, как Haskell или Elm. Однако этот термин чаще всего появляется в интерфейсной экосистеме, поэтому давайте проясним его применение в JavaScript.

Для согласования всего, что вы, возможно, читали о FRP в JS, важно понять, что Rx, Bacon.js, Angular и другие не согласуются с двумя основными принципами определения FRP Конала Эллиота

Функциональное реактивное программирование в своей реализации в JavaScript относится к программированию в функциональном стиле при создании и реагировании на потоки. Это довольно далеко от оригинальной формулировки Эллиота (которая специально исключает потоки как компонент), но тем не менее вдохновляется традиционными FRP.

Также очень важно понять, что JavaScript по сути взаимодействует с пользователем и пользовательским интерфейсом, DOM и часто с базой данных. Побочные эффекты и императивный код де факто являются для него стандартом, даже при использовании функционального или функционального реактивного подхода

Без императивного или нечистого кода веб-приложение JS с пользовательским интерфейсом не было бы очень полезным, поскольку оно не могло бы взаимодействовать со своей средой.

Давайте взглянем на пример, чтобы продемонстрировать основные принципы FRP-вдохновленного JavaScript. Этот пример использует RxJS и печатает движения мыши в течение десяти секунд:

Вы можете проверить этот код в действии в JSFiddle: FRP-вдохновленном JavaScript. Запустите скрипт и, пока идет подсчет до 10, наведите указатель мыши в экран с результатом. Вы должны увидеть координаты мыши вместе со счетчиком. Тогда на экран выведется, где была ваша мышь во время каждого 1-секундного интервала времени.

Давайте кратко обсудим эту реализацию шаг за шагом.

Сначала мы создаем observable . Это таймер, добавляющий значение в коллекцию каждые (каждую секунду). Нам нужно обработать событие таймера, чтобы извлечь его значение и добавить к результирующему потоку.

Затем мы создаем observable из события . Движение мыши продолжительно во времени. В любой точке последовательности существует бесконечное количество точек между ними. Мы ограничиваем эту бесконечность, так что результирующий поток становится более управляемым. Затем мы обрабатываем событие и возвращаем объект с значениями и , чтобы сохранить координаты мыши.

Затем мы хотим объединить потоки и . Для этого используем оператор объединения. Так мы можем определить, какие движения мыши произошли в течение каждого интервала времени. Мы будем называть результирующий observable . Мы также ограничим observable так, чтобы он завершился через десять секунд ().

Теперь, когда у нас есть объединенный поток времени и движения, мы создаем на observable , чтобы мы могли реагировать на него. В обратном вызове мы проверяем, является ли значение числом или нет. Если это так, мы вызываем функцию . Если это объект координат, вызываем . В обратных вызовах и мы просто логируем информацию.

Давайте рассмотрим функцию . Мы создаем новый элемент для каждого второго интервала, помечаем его и добавляем в DOM.

В функции мы выводим в последние координаты в последнем временном интервале. Это связывает каждый набор координат с соответствующим временным интервалом. Теперь мы можем увидеть, где мышь находилась в конкретный момент времени.

Примечание: эти функции нечистые: у них нет возвращаемого значения и они производят побочные эффекты. Побочные эффекты — манипуляции DOM. Как упоминалось ранее, JavaScript, который мы пишем для наших приложений, часто взаимодействует с областью видимости вне его функций.

Заключение

Мы наконец-то перешли к практике и увидели, для чего нужны пайпы и операторы. Рассмотрели, как можно сократить код, пользуясь богатым API, которое предоставляет нам RxJS. Конечно, наше приложение не закончено, в следующей части мы разберем, как можно в одном потоке обрабатывать другой и как отменять наш http запрос, чтобы еще больше экономить трафик и ресурсы нашего приложения. А чтобы вы могли увидеть разницу, я выложил пример без использования RxJS, посмотреть его можно здесь. По этой ссылке вы найдете полный код текущего приложения. Для генерации схем я пользовался RxJS визуализатором.

Подводя итог: observables

Observables являются потоками. Мы можем наблюдать любой поток: от событий изменения размеров в существующих массивах до ответов API. Мы можем создать observables практически из всего, что угодно. Promise — тот же observable, но отдающий только одно значение, а observables могут возвращать много значений с течением времени.

Мы можем работать с observables по-разному. RxJS использует множество операторов. Observables часто визуализируется с помощью точек на линии, как показано на сайте RxMarbles. Поскольку поток состоит из асинхронных событий с течением времени, легко осмыслять это линейным способом и использовать именно такие зрительные образы, чтобы понять реактивные операторы. Например, следующие изображение от RxMarbles иллюстрирует :

Дополнительную информацию об observables можно найти на следующих ресурсах:

  • Реактивные расширения: observables
  • Создание и подписка на простые последовательности observable
  • Представляем observable
  • RxMarbles
  • Rx Book — observable
  • Представление observables

Реактивное программирование

Реактивное программирование связано с декларативным (что делать, а не как) наблюдением и реагированием на поступающие события во времени.

Реактивное программирование часто связано с Reactive Extensions, API для асинхронного программирования с observable потоками. Реактивные расширения (с префиксом Rx) предоставляют библиотеки для различных языков, включая JavaScript (RxJS).

Подводя итог: функциональное реактивное программирование

FRP представляет собой написание действий, которые, используя чистые функции, реагируют на события и переводят состояние с предыдущего момента времени к следующему. FRP в реализации JavaScript не придерживается двух основных принципов FRP Конала Эллиота, но в абстрагировании от оригинальной концепции есть определенный смысл. JavaScript сильно зависит от побочных эффектов и императивного программирования, но мы, безусловно, можем использовать преимущества концепций FRP для улучшения нашего JS.

Наконец, рассмотрим эту цитату из первого издания Eloquent JavaScript: «Fu-Tzu написал небольшую программу, использующую глобальное состояние и сомнительные переплетения, и, прочитав ее, студент спросил:« Вы предупреждали нас против этих методов, но я нахожу их в вашей программе. Как такое могло случиться?». Фу-Цзы ответил: «Нет необходимости забирать водяной шланг, когда дом не горит». .

Дополнительную информацию о функциональном реактивном программировании можно найти на следующих ресурсах:

  • Функциональное реактивное программирование для начинающих
  • Функциональное реактивное заблуждение
  • Haskell — функциональное реактивное программирование
  • Создание реактивной анимации
  • Более элегантная спецификация для FRP
  • Elm — прощание с FRP
  • Ранние успехи и новые направления в функциональном реактивном программировании
  • Разрушение FRP
  • Rx не является FRP

Заключение

Мы закончим еще одной отличной цитатой из первого издания Eloquent JavaScript: «Студент долгое время сидел за своим компьютером, мрачно хмурился и пытался написать красивое решение сложной проблемы, но не мог найти правильный подход. Фу-Цу ударил его по затылку и крикнул: ‘Введите что-нибудь!’. Студент начал писать уродливое решение, и после того, как он закончил, он внезапно понял прекрасное решение».

Понятия, необходимые для понимания функционального программирования, реактивного программирования и функционального реактивного программирования, может быть трудно осознать, не говоря уже о полном овладении. Написание кода, использующего основные принципы какой-либо парадигмы — это первый шаг, даже если он вначале не является полностью верным. Практика освещает путь вперед, а также помогает пересматривать эти концепции.

Используя этот Справочник в качестве отправной точки, вы можете начать использовать представленные концепции и парадигмы программирования для повышения своего уровня владения JavaScript. Если по описанным темам что-либо еще неясно, пожалуйста, обратитесь к ссылкам в каждом разделе за дополнительными ресурсами. Позже мы рассмотрим новые концепции в следующей статье Справочника современных концепций JavaScript!


С этим читают