Реактивное программирование простыми словами — объясняют эксперты
Авторизуйтесь
Реактивное программирование простыми словами — объясняют эксперты
В предыдущих статьях эксперты объяснили нам, что такое объектно-ориентированное, динамическое, декларативное и императивное программирование. В этот раз узнали у них, что из себя представляет реактивное программирование.
технический директор центра инновационных технологий и решений «Инфосистемы Джет»
Классический подход к программированию предполагает выполнение запроса (к базе, сервисам и т.д.), ожидание ответа и продолжение работы. Во время ожидания поток простаивает. Масштабируют такие системы путём увеличения количества потоков, при этом вычисление оптимального количества потоков становится непростой задачей, так как сильно зависит от характера нагрузки, количества и качества ожиданий. И несмотря на это, избежать на 100% простоя ресурсов не удается.
В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На возникновение этого события реагирует соответствующий обработчик и выполняет свою задачу, опять генерирует событие и общий процесс обработки продолжается. Генерация события и реакция на него происходят асинхронно. Обработчик (их ещё называют акторами) выбирает следующее событие из очереди, обрабатывает и складывает событие в очередь другому обработчику. Если задача заключается в выполнении запроса в базе данных, то актор посылает запрос на сервер. После получения ответа будет сгенерировано событие с результатом запроса и запущен соответствующий актор.
Всё это позволяет более эффективно занять ресурсы полезной работой и управлять масштабированием. Повышается отзывчивость приложений. Кроме того, реактивное программирование помогает масштабироваться горизонтально.
senior developer IT-компании Maxima
Понятие реактивного программирования тесно связано с понятием модели распространения данных, которая бывает двух типов:
На самом деле, эти модели вполне логичны и естественны, поэтому их проявления можно проследить даже в обычной жизни.
Допустим, Вася любит быть в курсе всех новостей по хоккею и поэтому периодически посещает тематические сайты в надежде обнаружить интересный контент.
Его коллеге — Пете — тоже интересен хоккей, но в отличие от Васи, Петя подписался на рассылку новостей и получает уведомления о важных событиях в хоккее.
С точки зрения программирования, Вася использует pull-модель: периодически просматривает источники данных, в то время как Петя — push-модель: занимается обработкой входящих сообщений.
Таким образом, реактивное программирование — это стиль написания кода, который упрощает реализацию приложений, основанных на push-модели.
На практике этот стиль применяется для обработки входящего потока данных, например:
руководитель направления платформы Bercut
Реактивное программирование — это подход к разработке ПО, который строится на реагировании на события и на распространении событий. При этом модель реакции на события предполагает возможность простого распространения этих или трансформированных событий далее по системе. Ярким примером реализации реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек в цепочке значения в зависимых ячейках пересчитываются автоматически.
В целом, идея реактивного программирования призвана упростить создание сложных систем. В сложной большой системе возникает значительное количество разнообразных событий, каждое требует определённого механизма реакции и обработки. При использовании реактивного программирования события объединяются в потоки, а компоненты системы являются обработчиками потока событий (и также в свою очередь могут являться генераторами событий). Таким образом, сколь ни была бы сложна система и сколько бы в ней ни было разнообразных событий, вся система строится по принципу генерации потоков событий и реакции на них. Подход в моделировании сложной системы позволяет обрабатывать каждое событие асинхронно и изолированно от других. Важно понимать при этом, что дизайн реактивной системы предполагает, что любая функциональность в системе реализуется с помощью событий и обработчиков. Благодаря этому достигается уменьшение связанности между компонентами системы, увеличение гибкости, упрощение масштабирования и повышение устойчивости систем.
руководитель мобильной разработки SimbirSoft
Парадигма реактивного программирования включает отслеживание определенных событий и реагирование на них в асинхронных потоках данных. Эта идея предполагает, что существует источник событий и слушатель событий, который реагирует на события источника.
Суть реактивного программирования можно изобразить разными способами. Например, представим реку, течение которой несет несколько разноцветных объектов (мячей). Допустим, на берегу сидит человек, которому нужно выловить объекты с определенной характеристикой – только зеленые или красные. Если нам требуется запрограммировать подобную ситуацию, то реактивный подход – это то, что поможет нам оперировать потоками данных, получать данные, совершать математические вычисления и т.д. Работа с потоками требует от программиста определенного опыта и понимания, что и как комбинировать, какие операторы подходят для решения задачи.
Реактивный подход активно используется в Frontend-разработке и мобильной разработке, одним из его популяризаторов является Netflix.
Вступление в Реактивное Программирование, которое вы пропустили
Ну что ж, вы решили выучить новую вещь, которая называется Реактивное программирование (Reactive Programming), а в частности — его имплементацию в виде Rx, Bacon.js, RAC или чего-то другого.
Обучение — сложный процесс, который становится еще труднее, когда нету подходящего материала. И в начале моего обучения, я пытался найти какие-то туториалы. Но все что я находил были частичные гайди, которые носили поверхностных характер и не давали целостного представления о построении архитектуры. А документация по библиотекам не особо помогла при понимании некоторых функций:
Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element’s index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
Я прочитал две книги, первая из которых всего лишь описывала общую картину, в то время, как вторая описывала использование конкретной библиотеки. Окончание обучения реактивному программированию было сложно — приходилось разбираться с ним в процессе работы. В Futurice я применил его в реальном проекте при поддержки некоторых своих коллег.
Самой сложной частью процесса обучения оказалось научить свой мозг работать в «реактивном» стиле, имея старые императивные привычки и стойкие шаблоны разработки. К сожалению я не нашел уроков в интернете, которые описывали бы этот аспект и я подумал, что мир заслуживает несколько слов о том, как думать в реактивном стиле. В общем, можем начинать. А документация библиотек может помочь продолжить полет после моей статьи. Я надеюсь, что я вам помогу.
Что такое Реактивное Программирование?
В интернете существует много плохих объяснений и определений реактивного программирования. Например Википедия как всегда все обобщает и теоретизирует, а Stackoverflow содержит каноничные ответы, которые не подходят новичку. Reactive Manifesto звучит, как одна из тех вещей, которые нужно показывать своему проектному менеджеру или бизнес аналитику своей компании. А Rx terminology «Rx = Observables + LINQ + Schedulers» настолько тяжел и майкрософтный, что большинству из нас остается только возмущаться. Слоганы вроде «реактивный» и «распространение изменений» не объясняют ничего конкретного, что отличало б типичный подход MV*, который уже встроен в ваш язык. Конечно же представления из моего фреймворка реагируют на модели. Конечно же распространение изменений. Если б это было не так — то мы б не увидели работу программы.
Ну что ж, давайте расставим точки над i.
Реактивное программирование — это программирование с асинхронными потоками(streams) данных.
В общем говоря, здесь нету ничего нового. Шины событий или типичные события нажатий на кнопки — это все реальные примеры асинхронных событийных потоков, которые вы можете слушать и выполнять некоторые побочные действия. По сути — реактивность эксплуатирует эту идею на стероидах. Вам предоставляется возможность создавать потоки чего-либо, не только события нажатий. Потоки легковесные и используются повсеместно: переменные, пользовательский ввод, свойства, кеш, структуры данных и многое многое другое. Например, лента Твитера может быть потоком данных наравне с чередой событий пользовательского интерфейса. То есть можно слушать поток и реагировать на события в нем.
Более того, вы получаете прекрасный набор инструментов и функций для сочетания, которые позволяют создавать и фильтровать каждый из этих потоков. Вот где «функциональная» магия дает о себе знать. Потоки могут быть использованы как входные параметры друг друга. Даже множественный поток может быть использован как входной аргумент другого потока. Вы можете объединять несколько потоков. Вы можете фильтровать один поток, чтобы потом получить другой, который содержит только актуальные данные. Вы можете объединять данные с одного потока с данными другого, чтобы получить еще один.
Так вот, если потоки — это центральная идея Реактивности, давайте более пристально рассмотрим их, начнем с знакомого нам событийного потока «нажатия на кнопку».
Поток — это последовательность, состоящая из постоянных событий, отсортированных по времени. В нем может быть три типа сообщений: значения (данные некоторого типа), ошибки и сигнал о завершении работы. Рассмотрим то, что сигнал о завершении имеет место для экземпляра объекта во время нажатия кнопки закрытия.
Мы получаем эти cгенерированные события асинхронно, всегда. Согласно идеологии реактивного программирования существуют три вида функций: те, которые должны выполняться, когда некоторые конкретные данные будут отправлены, функции обработки ошибок и другие функции с сигналами о завершении работы программы. Иногда последнее два пункта можно опустить и сосредоточится на определении функций для обработки значений. Слушать(listening) поток означает подписаться(subscribing) на него. То есть функции, которые мы определили это наблюдатели(observers). А поток является субъектом который наблюдают. Такой подход называется Observer Design Pattern.
Альтернативным способом представить вышеупомянутую диаграмму является ASCII графика, которую мы будем использовать в некоторых разделах этого туториала:
Чтобы не дать вам заскучать давайте разберем что-то новое, например создадим поток событий, преобразовав изначальный поток событий нажатий.
Первое что мы сделаем — добавим счетчик, который будет индикатором нажатий кнопки. В большинстве Реактивных библиотек каждый поток имеет много встроенных функций, таких как объединение, фильтр, сканер и так дальше. Когда вы вызываете одну из этих функций, таких как clickStream.map(f), она возвращает новый поток, который базируется на родительском(на clickStream). Дочерний поток никаким образом не затрагивает и не модифицирует своего родителя. Это свойство называется постоянностью(immutability) и является неотъемлемой частью реактивных потоков, так само как блинчики нельзя себе представить без сиропа. Это разрешает нам объединять функции(например — clickStream.map(f).scan(g)):
Функция map(f) создает новый поток, в котором с помощью функции f заменяться каждое новое событие. В нашем случае мы привязываем единицу к каждом нажатию на кнопку. Функция scan(g) агрегирует все предыдущие значение в потоке, возвращая значение x = g(accumulated, current). После этого counterStream посылает общее количество нажатий.
Для того, чтобы показать реальную силу Реактивного подхода, давайте просто скажем, что вы хотите получить поток, который будет вызывать двойное нажатие. Для того, чтобы сделать все более интересным, давайте скажем, что мы хотим чтобы новый поток рассматривал тройные нажатия как двойные, или, в общем, как мультинажатия(два или больше). Сделайте глубокий вдох и представьте что вы моли бы сделать то же самое в традиционной императивной манере. Я держу пари, что это звучит довольно мерзко и включает в себя переменные для сохранения некоторых состояний и временных интервалов.
То-есть, в Реактивном программирование все очень просто. По сути логика сосредоточена в 4 строчках кода
. Но давайте не будем обращать внимание на код, пока что. Размышление над диаграмой — лучший способ для понимания и построение потоков, без разницы, являетесь ли вы экспертом или только начинаете.
Серые прямоугольники являются функциями трансформации одного потока в другой. Первое, что мы сделали — аккумулировали клики в список. Всякий раз, когда 250 миллисекунд задержки события проходят (вот почему buffer(stream.throttle(250ms)), генерируется событие. Не переживайте насчет понимания деталей этого момента. Мы только разбираемся с Реактивностью. Результатом является поток списка, где к каждому элементу была применена функция map(), чтобы присоединить к каждому списку его длину. И наконец мы игнорируем число 1, используя функцию filter(x >= 2). Это все — всего 3 операции для того что бы создать наш целевой поток. Мы можем подписать на него листенер, который будет реагировать в точности так, как мы захотим.
Я надеюсь, что вы получили наслаждение от красоты этого подхода. Этот пример — всего лишь верхушка айсберга: вы можете применять те же операции к разным типам потоков данных, например к потокам ответа API. Ну и к тому же впереди еще огромное количество других функций.
Введение в реактивное программирование
Здравствуйте. В этой статье я пробегусь галопом по Европам, а именно — расскажу, что понимают под реактивным программированием, познакомлю с акторами, реактивными потоками, и наконец, при помощи реактивных потоков мы сделаем распознавание мышиных жестов, как в старой Opera и её духовном наследнике — Vivaldi.
Цель — познакомить с основными концепциями реактивного программирования и показать, что не всё так сложно и страшно, как может показаться на первый взгляд.

Источник
Что такое реактивное программирование?
Чтобы ответить на этот вопрос, обратимся к сайту. На нём есть красивая картинка, на которой показаны 4 основных критерия, которым должны соответствовать реактивные приложения.
Приложение должно быть быстрым, отказоустойчивым и хорошо масштабироваться.
Выглядит как «мы за всё хорошее против всего плохого», верно?
Что подразумевается под этими словами:
Модель акторов
Основные вехи развития:
Что умеют акторы?
Акторы — это те же объекты, но:
Актор А хочет отправить сообщение актору Б. Всё, что у него есть — ActorRef (некий адрес). Актор Б может находиться где угодно.
Актор А отправляет письмо Б через систему (ActorSystem). Система кладёт письмо в почтовый ящик актора Б и «будит» актор Б. Актор Б берёт письмо из ящика и что-то делает.
По сравнению с вызовом методов у другого объекта, выглядит излишне сложно, но модель акторов прекрасно ложится на реальный мир, если представить, что акторы — это люди, которые обучены что-то делать в ответ на определённые раздражители.
Представим себе отца и сына:
Отец шлёт сыну СМСку «Убери в комнате» и продолжает заниматься своими делами. Сын читает СМСку и начинает уборку. Отец тем временем играет в покер. Сын заканчивает уборку и шлёт СМС «Готово». Выглядит просто, верно?
Теперь представим, что отец и сын не акторы, а обычные объекты, которые могут дёргать методы друг у друга. Отец дёргает сына за метод «убери в комнате» и следует за ним по пятам, ожидая, пока сын не закончит уборку и не передаст управление обратно отцу. Играть в покер в это время отец не может. В этом контексте модель акторов становится более привлекательной.
Akka.NET
Всё, что написано ниже, справедливо и для оригинального Akka для JVM, но для меня C# ближе, чем Java, поэтому я буду рассказывать на примере Akka.NET.
Итак, какие преимущества есть у Akka?
Обработка ошибок
У акторов есть иерархия — её можно представить в виде дерева. У каждого актора есть родитель и могут быть «дети».
Akka.NET documentation Copyright 2013-2018 Akka.NET project
Для каждого актора можно установить Supervision strategy — что делать, если у «детей» что-то пошло не так. Например, «прибить» актор, у которого возникли проблемы, а затем создать новый актор того же типа и поручить ему ту же работу.
Для примера я сделал на Akka.net CRUD приложение, в котором слой «бизнес-логики» реализован на акторах. Задачей этого проекта было узнать, стоит ли использовать акторы в немасштабируемых системах — сделают ли они жизнь лучше или добавят ещё боли.
Как может помочь встроенная обработка ошибок в Akka:
И немного кода. Так выглядит инициализация системы акторов в IoC контейнере:
EchoActor — самый простой актор, который возвращает значение отправителю:
Для связи акторов с «обычным» кодом используется команда Ask:
Итого
Похимичив с акторами, могу сказать:
Часть 2: Реактивные потоки
А теперь перейдём к более популярной и полезной теме — реактивным потокам. Если с акторами в процессе работы можно никогда не повстречаться, то Rx потоки обязательно пригодятся как во фронтенде, так и в бэкенде. Их реализация есть почти во всех современных языках программирования. Я буду приводить примеры на RxJs, так как в наше время даже бэкенд программистам порой приходится что-то делать на JavaScript.

Rx-потоки есть для всех популярных языков программирования
Чтобы объяснить, что такое реактивный поток, я начну с Pull и Push коллекций.
| Single return value | Multiple return values | |
|---|---|---|
| Pull Synchronous Interactive | T | IEnumerable |
| Push Asynchronous Reactive | Task | IObservable |
Pull коллекции — это то, к чему мы все привыкли в программировании. Самый яркий пример — массив.
В нём уже есть данные, сам он эти данные не поменяет, но может отдать по запросу.
Также перед тем, как что-то делать с данными, можно их как-то обработать.
А теперь давайте представим, что изначально в коллекции нет данных, но она обязательно сообщит о том, что они появились (Push). И в то же время мы всё так же можем к этой коллекции применять нужные трансформации.
Когда в source появится значение, например, 1, console.log выведет “my number is 1”.
Появляется новая сущность — Subject (или Observable):
Это и есть push-коллекция, которая будет рассылать уведомления об изменении своего состояния.
В данном случае в ней сразу появятся числа 1, 2 и 3, через секунду 4, а затем коллекция «завершится». Это такой особый тип события.
Вторая сущность — это Observer. Он может подписаться на события Subject’a и что-то сделать с полученными данными. Например:
Тут видно, что у одного Subject может быть много подписчиков.
Выглядит несложно, но пока не совсем понятно, для чего это нужно. Я дам ещё 2 определения, которые нужно знать, работая с реактивными потоками, а затем покажу на практике, как они работают и в каких ситуациях раскрывается весь их потенциал.
Cold observables
Hot observables
В каких ситуациях использовать реактивные потоки?
Когда есть поток данных, распределённый во времени. Например, пользовательский ввод. Или логи из какого-либо сервиса. В одном из проектов я видел самописный логгер, который собирал события за N секунд, а затем единовременно записывал всю пачку. Код аккумулятора занимал страницу. Если бы использовались Rx потоки, то это было бы намного проще:
“RxJs Reference / Observable, documentation licensed under CC BY 4.0.
(тут много примеров и картинки, поясняющие, что делают различные операции с реактивными потоками)
И, наконец, пример использования.
Распознавание мышиных жестов при помощи Rx потоков
В старой Опере или её духовном наследнике — Vivaldi — было управление браузером при помощи мышиных жестов.
То есть нужно распознавать движения мышью вверх/вниз, вправо/влево и их комбинации. Это можно написать без Rx потоков, но код будет сложным и трудноподдерживаемым.
А вот как это выглядит с Rx потоками:
Начну с конца — задам, какие данные и в каком формате я буду искать в исходной последовательности:
Это единичные векторы и их комбинации.
Далее нужно преобразовать события мыши в Rx потоки. Во всех Rx библиотеках есть встроенные инструменты для превращения стандартных ивентов в Observables.
Далее, я группирую координаты мыши по 2 и нахожу их разницу, получая смещение мыши.
И группирую эти движения, используя события ‘mousedown’ и ‘mouseup’.
Функция concat вырезает слишком короткие движения и группирует движения, примерно совпадающие по направлению.
Если движение по оси X или Y слишком короткое, оно обнуляется. А затем от полученных координат смещения остается только знак. Таким образом, получаются единичные векторы, которые мы искали.
При помощи sequenceEqual можно сравнить полученные движения с исходными и, если есть совпадение, выполнить определённое действие.
Обратите внимание, что, кроме распознавания жестов, здесь есть ещё отрисовка как изначальных, так и нормализованных движений мыши на HTML canvas. Читаемость кода от этого не страдает.
Из чего следует ещё одно преимущество — функционал, написанный при помощи Rx потоков, может быть легко дополнен и расширен.
Реактивное программирование на реальных примерах: подробное введение
Авторизуйтесь
Реактивное программирование на реальных примерах: подробное введение
Обучение реактивному подходу в программировании — достаточно непростая вещь, и недостаток обучающих материалов только усугубляет этот процесс. Большинство существующих обучающих пособий не дают глубокого обзора и не рассказывают о том, как спроектировать архитектуру проекта в целом.
Этот материал направлен на то, чтобы помочь новичкам начать думать по-настоящему «реактивно».
Так что же такое реактивное программирование?
Есть множество не до конца верных определений и объяснений в интернете. Википедия дает слишком скупое описание. Ответы на Stack Overflow часто непонятны новичкам. Реактивный Манифест выглядит так, будто его писали для руководителей проектов или бизнесменов. Rx терминология от Microsoft, гласящая о том, что «Rx = Observables + LINQ + Schedulers», звучит настолько тяжело и по-майкрософтовски, что большинство из нас слабо понимает, о чем идёт речь. Такие термины, как «реактивность» и «распространение изменений» не выражают ничего, что бы отличалось от обычного MV* подхода, реализованного уже на бесчисленном множестве языков. Любой фреймворк реагирует на изменения моделей. В любом фреймворке изменения распространяются. Если бы это было не так, пользователь не видел бы никаких изменений.
Дадим подробное объяснение термину «реактивное программирование».
Реактивное программирование — программирование с асинхронными потоками данных
Впрочем, ничего нового. Event bus’ы или обычные события клика — это тоже асинхронные потоки данных, которые вы можете прослушивать, чтобы реагировать какими-либо действиями. Реактивность — это та же самая идея, возведенная в абсолют. Вы можете создавать потоки данных не только из событий наведения или кликания мышью. Потоком может быть что угодно: переменные, пользовательский ввод, свойства, кэш, структуры данных и т.п. Например, представьте, что ваша лента новостей в Твиттере — поток событий. Вы можете слушать этот поток и реагировать на события соответственно.
Кроме этого, вы получаете удивительный набор функций для комбинирования, создания и фильтрации этих потоков. Вот где проявляется вся магия этого подхода. Один или несколько потоков могут использоваться как входные данные для другого потока. Вы можете объединять два потока. Также вы можете фильтровать поток, выбирая только те события, которые вам интересны.
Так как потоки — основопологающая вещь в реактивном подходе, давайте рассмотрим их подробнее на примере пользовательского клика мышью:
Поток — это последовательность событий, упорядоченная по времени. Он может выбрасывать три типа данных: значение (определенного типа), ошибку или сигнал завершения. Сигнал завершения распространяется, когда текущее окно или окно, содержащее кнопку, закрывается.
Мы перехватываем эти события асинхронно, указывая одну функцию, которая будет вызываться, когда выброшено значение, другую для ошибок и третью для обработки сигнала завершения. В некоторых случаях можно опустить последние две и сфокусироваться на объявлении функции для перехвата значений. Прослушивание потока называется подпиской (subscribing). Функции, которые мы объявляем, называются наблюдателями (observer). Поток — это объект наших наблюдений (observable, наблюдаемый объект). Это в точности паттерн проектирования, называемый «Наблюдатель«. Подробнее о шаблонах проектирования для новичков, читайте в нашей статье.
В данном руководстве мы будем использовать альтернативный способ представления вышеупомянутой диаграммы с помощью ASCII символов:
Теперь давайте сгенерируем новые потоки сообщений клика, трансформированные из оригинального потока.
Чтобы вы поняли всю мощь реактивного подхода, давайте предположим, что вы хотите реализовать поток событий «двойной клик». Чтобы сделать эту задачу еще интереснее новый поток должен принимать множественные нажатия за двойные. Представьте себе, как бы вы реализовывали эту задачу в императивном стиле. Потребовалось бы несколько переменных, хранящих состояние, и использование интервалов.
В реактивном же подходе все достаточно просто. Всю описанную выше логику можно реализовать четырьмя строками кода. Но давайте пока не останавливаться на коде. Лучший способ научиться понимать и проектировать потоки, вне зависимости от того, новичок вы или эксперт — это изображать диаграммы:
Этот пример показывает всю простоту, с которой реализовывается достаточно сложная на первый взгляд задача, если мы используем реактивный подход.
Для чего нужно реактивное программирование
Реактивный подход повышает уровень абстракции вашего кода и вы можете сконцентрироваться на взаимосвязи событий, которые определяют бизнес-логику, вместо того, чтобы постоянно поддерживать код с большим количеством деталей реализации. Код в реактивном программировании, вероятно, будет короче.
Преимущество более заметно в современных веб- и мобильных приложениях, которые работают с большим количеством разнообразных UI-событий. 10 лет назад всё взаимодействие с веб-страницей сводилось к отправке больших форм на сервер и выполнении простого рендеринга в клиентской части. Сейчас приложения более сложны: изменение одного поля может повлечь за собой автоматическое сохранение данных на сервере, информация о новом «лайке» должна отправиться другим подключенным пользователям и т.д.
Реактивное программирование очень хорошо подходит для обработки большого количества разнообразных событий.
Начинаем думать в реактивном стиле
В последующих примерах используется JavaScript и RxJS, но Rx-библиотеки доступны для многих других языков и платформ (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy, и т.д.). На нашем сайте есть руководства по использованию библиотек RxSwift и ReactiveX в Python.
Реализуем виджет «На кого подписаться»
В Twitter есть такой виджет, который предлагает вам другие аккаунты, на которые вы можете подписаться:
Мы намерены реализовать его основную функциональность:
Вместо Twitter-аккаунтов, которые закрыты для неавторизованных пользователей, мы будем использовать Github API и брать аккаунты оттуда. Ссылку на Github API для получения списка пользователей вы можете найти в официальной документации. Также можете смотреть на готовый код данного примера.
Запрос и ответ
Как подойти к решению этой проблемы в Rx-стиле? Надо начать с того, что (почти) все, что угодно может быть потоком. Первое, что мы реализуем, будет «Загрузка из API и вывод трех аккаунтов». Ничего необычного, нужно просто (1) сделать запрос, (2) получить ответ, (3) отобразить ответ. Представим запрос в качестве потока.
При инициализации мы должны сделать только один запрос, так что если мы смоделируем его, как поток данных, он будет выбрасывать только одно значение. В дальнейшем мы будем делать множество запросов, но на данный момент нам нужен только один.
Когда происходит запрос, он сообщает нам две вещи: когда запрос должен быть выполнен — время генерации события, и куда мы делаем запрос — значение генерируемого событие, строка, содержащая URL.
Создать поток, содержащий одно значение, очень просто с библиотеками семейства Rx*:
То, что мы написали — это просто поток, содержащий строку, который не делает ничего, так что мы должны как-то заставить его действовать так, как нам нужно. Это делается с помощью подписки на поток:
Заметьте, что мы используем Ajax-коллбэк (callback) из библиотеки jQuery, чтобы управлять асинхронностью операции запроса. Если вы слабо понимаете, что такое callback’и, почитайте нашу статью об эволюции асинхронного программирования в JS. «Но подождите, Rx же работает с асинхронными потоками данных. Не может ли ответ на запрос быть потоком, содержащим данные, которые придут когда-нибудь позже?» — можете спросить вы. Что ж, на концептуальном уровне все верно, давайте попробуем это реализовать:
Rx.Observable.create() создает пользовательский поток данных, информируя каждого подписчика о событиях ( onNext() ) или ошибках ( onError() ). Мы обернули Ajax-промис в соответствующий коллбэк. Значит ли это, что Promise — то же самое, что Observable? Да, значит. Подробнее о Promis’ах читайте в нашей вводной статье.
Observable — это Promise++. В Rx вы можете конвертировать Promise в Observable очень простым образом:
Единственное отличие между Promise и Observable в том, что Observable не совместим с Promises/A+. Promise — это, по сути, Observable с одним генерируемым значением. Потоки в Rx расширяют промисы, позволяя возвращать множество значений.
Одна из таких функций, с которой вы уже познакомились — map(f) — берет каждое значение из потока A, применяет на нем f() и производит значение для потока B. Если мы применим эту функцию на потоке запроса и ответа, мы можем преобразовать список URL’ов в промисы ответа.
Затем мы должны создать поток потоков, называемый так же метапоток (metastream). Не пугайтесь, все достаточно просто. Метапоток — это такой поток, в котором каждое генерируемое им значение является потоком. Можно представить их себе как указатели: каждое генерируемое значение — указатель на новый поток. В нашем примере URL каждого запроса преобразуется в указатель на поток, содержащий промис ответа.
Как и ожидалось, если у нас впоследствии будут какие-то события, генерируемые потоком запросов, поток ответов будет реагировать на них соответствующим образом:
И теперь, когда у нас есть поток ответов, мы можем отрендерить получаемые данные:
Кнопка обновления
Нужно отметить, что список пользователей, который мы получаем по API, состоит из 100 элементов. API позволяет нам задавать смещение списка, но не его размер, так что пока мы используем только 3 объекта, игнорируя остальные. Вы научитесь кэшировать ответ чуть позже.
Каждый раз, когда пользователь нажимает кнопку обновления, поток запросов должен сгенерировать URL, чтобы мы могли получить новые данные. Для этой задачи нам потребуется сделать две вещи: реализовать поток событий нажатия на кнопку, а также изменить поток запросов так, чтобы он реагировал на события потока нажатий. К счастью, RxJS позволяет нам преобразовать обычные JavaScript-события в Observable.
Давайте поменяем поток запросов так, чтобы при нажатии кнопки обновления генерировался URL со случайным параметром смещения списка.
Похоже, мы что-то сломали. Теперь запрос срабатывает только после того, как мы нажали кнопку. Но по условию задачи мы должны делать запрос и при инициализации. Давайте попробуем починить наш код.
Для начала создадим разные потоки для вышеупомянутых условий:
Ну, теперь все очень просто:
Также есть альтернативный и более чистый способ реализовать задачу без вспомогательных переменных:
А можно и еще короче!
Моделируем 3 рекомендации с помощью потоков
Теперь, вместе с кнопкой обновления, у нас появилась проблема: при нажатии этой кнопки текущие 3 рекомендации не исчезают. Новые предложения появляются, как только с сервера пришел ответ, но для того, чтобы наш UI выглядел отзывчивым, мы должны очищать текущие предложения сразу же.
Теперь у нас есть два подписчика, влияющих на DOM-элементы (другой подписывается на responseStream ) и это соответствует принципу «Разделяй и властвуй«. Вы еще помните мантру реактивного подхода? Напоминаем:
Все является потоком
Давайте сделаем так, чтобы рекомендации были потоками, в которых каждое генерируемое значение — это JSON-объект, содержащий данные рекомендации. Мы сделаем по потоку на каждую из трех рекомендаций. Вот так выглядит поток для рекомендации №1:
Скопипастим этот код для потока №2 ( suggestion2Stream ) и №3 ( suggestion3Stream ). Подумайте над тем, как можно избежать дублирования кода в данном примере. Это будет отличным упражнением. А еще и очень хорошим способом избежать эффекта последней строки.
Теперь мы можем обнулять рекомендацию при нажатии кнопки обновления:
Вот диаграмма того, что мы только что реализовали:
В качестве бонуса мы также можем выводить «пустые» рекомендации при инициализации с помощью startWith(null) :
Удаление рекомендации и кэширование ответа сервера
Последняя функция, которую мы реализуем — удаление рекомендации. Напротив каждой рекомендации есть кнопка удаления, которая закрывает текущую рекомендацию и показывает вместо неё новую. Первая мысль, которая может у вас возникнуть — нужно делать новый запрос по нажатию этой кнопки:
Это не сработает. Перезагрузятся все рекомендации. Существует несколько способов решения этой проблемы, и один из наиболее оптимальных — повторное использование предыдущего ответа сервера. Ответ API состоит из списка длиной в 100 элементов, из которых мы используем только 3. Нет надобности запрашивать новые данные, когда мы можем использовать 97 «свежих».
Когда происходит нажатие кнопки «close1», нам нужно использовать наиболее свежий ответ сервера, чтобы получить случайного пользователя из списка. Примерно так:
Есть несколько способов решения этой проблемы и мы воспользуемся простейшим: симулированием клика кнопки «close1» при инициализации:
Все, курс молодого бойца по реактивному программированию окончен! Вы можете взглянуть на рабочий код.













