Содержание
- 1 Brand New to RxJS?
- 2 Content
- 3 Introductory Resources
- 4 На практике: изменяемость в JavaScript
- 5 Что такое функциональное реактивное программирование?
- 6 Observable
- 7 Why use switchMap?
- 8 Getting Started
- 9 Why use shareReplay?
- 10 Unsubscribe
- 11 На практике: функциональное реактивное программирование на JavaScript
- 12 Заключение
- 13 Подводя итог: observables
- 14 Реактивное программирование
- 15 Подводя итог: функциональное реактивное программирование
- 16 Заключение
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));});
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!
С этим читают
- Урок №37. const, constexpr и символьные константы
- В чём разница между var, let и const в javascript
- Angular http get example using httpclient
- Php vs. javascript: a thorough comparison
- Возможности javascript, о существовании которых я не знал
- Верстка сайта — шпаргалка для начинающих
- Nuxt as fullstack server: frontend + backend api server (часть 1)
- Асинхронное программирование в javascript (callback, promise, rxjs )
- Список инструментов разработчика javascript
- Что нужно сделать, чтобы стать хорошим программистом