rxjs что это такое

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

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

В Javascript широко распространено асинхронное программирование (async/await). Асинхронность, с одной стороны, позволяет решать какие-то задачи отдельно от основного потока, а с другой — требует хорошего понимания процессов и контроля над ними для эффективного управления ресурсами. Одновременное выполнение сложных задач может привести к ухудшению производительности приложения. Основная проблема заключается в том, что асинхронным программированием сложно управлять, но здесь на помощь можно призвать RxJS.

Проблемы, которые решает RxJS

Операции ввода-вывода выполняются намного медленнее, чем обычные вычисления и манипуляции с данными.

Работа Javascript с внешними интерфейсами может приводить к большому количеству соединений веб-браузера с сервером и множеству обратных вызовов для обработки ответов. Этот путь фактически идет вразрез с практиками программирования, потому что не известно, сколько времени продлится процесс, нет возможности его полностью контролировать, но нужно реагировать на его завершение. В простых приложениях можно спокойно обходиться обратными вызовами (callback).

Promises в ES6 Javascript

В этом коде на первый взгляд всё не так плохо, но если внутри цикла for будет больше действий, он станет трудно читаемым и поддерживать его (что-то изменять, добавлять, убирать) станет сложнее.

Но вернёмся к RxJS, что это?

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

Потоки в RxJS

Поток (Stream) — это серия событий, происходящих за определенный период времени. Поток может использоваться для обработки любого типа событий: клики и скролл мыши, ввод с клавиатуры, обработка данных и т.д. Если представить поток, как переменную, она будет реагировать на любые изменения ее значений.

Переменная и поток — одинаково динамические по сути, но у них есть некоторые отличия. Рассмотрим простой пример:

Observable в RxJS

Объект Observable (наблюдаемый), наверное, самая важная часть в RxJS. Он используется для обработки цепочек событий, например, манипуляции с мышью (клики, курсор, скролл) или клавиатурой, перебор значений (числа, строки, объекты или массивы), которые помогают управлять этапами прохождения событий. Примитивной формой наблюдаемого объекта может быть одиночная переменная, например:

Вот так будет выглядеть приведённый ранее пример с реальными командами RxJS и его API:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Методы Observable

Вот некоторые методы:

of(arg) Преобразует любое переданное ему значение или значения, разделённые запятой, в объект Observable from(iterable) Преобразует итерируемые значения в объект Observable fromPromise(promise) Преобразует Promise в объект Observable fromEvent(element, eventName) Создаёт объект Observable, добавляет наблюдателя элементу и слушает события, например, DOM-events

Программирование потоков иначе обрабатывает возникновение события, Observable реализует «ленивое поведение», то есть вызов или подписка будет изолированной операцией: каждый вызов функций вызывает отдельный процесс, а каждая Observable-подписка запускает свой отдельный процесс.

Observer

Observer (наблюдатель) является получателем данных, он обрабатывает и реагирует на значения, передаваемые из Observable. API для Observer простой, функция next() используется для каждой итерации в последовательности. Она вызывается, когда Observable передаёт событие.

В предыдущем примере streamC$.subscribe(console.log) — это упрощенная версия, которая фактически использует базовую концепцию Observer. Итак, как создать Observer:

У Observer есть API обработки ошибок, который будет реагировать на возникновение ошибки во время выполнения. Все методы в наблюдателе необязательные и доступны для подписки. В методе next() следует поместить соответствующее для обработки значений поведение.

Итоги

Источник

Основы реактивного программирования с использованием RxJS

Часть 1. Реактивность и потоки

Данная серия статей посвящена реактивности и ее применению в JS с использованием такой замечательной библиотеки как RxJS.

Серия статей «Основы реактивного программирования с использованием RxJS»:

Для кого эта статья: в основном, здесь я буду объяснять основы, поэтому в первую очередь статья рассчитана на новичков в данной технологии. Вместе с тем надеюсь, что и опытные разработчики смогут почерпнуть для себя что-то новое. Для понимания потребуются знания js(es5/es6).

Мотивация: впервые я столкнулся с RxJS, когда начал работать с angular. Именно тогда у меня возникли сложности с пониманием механизма реактивности. Сложности прибавлял еще тот факт, что на момент начала моей работы большинство статей было посвящено старой версии библиотеки. Пришлось читать много документации, различных мануалов, чтобы хоть что-то понять. И только спустя некоторое время я начал осознавать, как “все устроено”. Чтобы упростить жизнь другим, я решил разложить все по полочкам.

Что такое реактивность?

Сложно найти ответ на, казалось бы, такой распространенный термин. Если кратко: реактивность — это способность реагировать на какие-либо изменения. Но о каких изменениях идет речь? В первую очередь, об изменениях данных. Рассмотрим пример:

Данный пример демонстрирует привычную нам императивную парадигму программирования. В отличие от императивного подхода, реактивный подход строится на push стратегии распространения изменений. Push стратегия подразумевает, что в случае изменения данных эти самые изменения будут “проталкиваться”, и зависимые от них данные будут автоматически обновляться. Вот как бы вел себя наш пример, если бы применялась push стратегия:

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

Поток данных

Если поискать в Википедии термин “реактивное программирование”, то сайт нам выдаст следующее определение: “Реактивное программирование — парадигма программирования, ориентированная на потоки данных и распространение изменений”. Из этого определения можно сделать вывод, что реактивность базируется на двух основных “китах”. Про распространение изменений я упоминал выше, поэтому дальше мы на этом останавливаться не будем. А вот про потоки данных следует поговорить подробнее. Посмотрим на следующий пример:

Мы слушаем событие keyup и кладем объект события в наш массив. Со временем наш массив может содержать тысячи объектов KeyboardEvent. При этом стоит отметить, что наш массив отсортирован по времени — индекс более поздних событий больше, чем индекс более ранних. Такой массив представляет собой упрощенную модель потока данных. Почему упрощенную? Потому что массив умеет только хранить данные. Еще мы можем проитерировать массив и как-то обработать его элементы. Но массив не может сообщить нам о том, что в него был добавлен новый элемент. Для того, чтобы узнать, были ли добавлены новые данные в массив, нам придется снова проитерировать его.

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

Observable

Теперь, когда мы знаем, что такое потоки, давайте поработаем с ними. В RxJS потоки представлены классом Observable. Чтобы создать свой поток, достаточно вызвать конструктор данного класса и передать ему в качестве аргумента функцию подписки:

Через вызов конструктора класса Observable мы создаем новый поток. В качестве аргумента в конструктор мы передали функцию подписки. Функция подписки — это обычная функция, которая в качестве параметра принимает наблюдателя(observer). Сам наблюдатель представляет собой объект, у которого есть три метода:

Subscription

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

Мы определили нашего наблюдателя и описали у него три метода: next, error, complete. Методы просто логируют данные, которые передаются в качестве параметров. Затем мы вызываем метод subscribe и передаем в него нашего наблюдателя. В момент вызова subscribe происходит вызов функции подписки, той самой, которую мы передали в конструктор на этапе объявления нашего потока. Дальше будет выполняться код функции-подписки, которая передает нашему наблюдателю два значения, а затем завершает поток.

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

Unsubscribe

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

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

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

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

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

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

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

Источник

RxJS: реактивное расширение для фронтенд разработки

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

Итак, прошу любить и жаловать, Виктор Русакович. Родом из Минска, работает в компании GP Software.travel.
Виктор последние пять лет занимается (в основном) фронт-енд разработкой. Ну а начинал, как и большинство из нас, с jQuery.

Потом был backbone, angular v1. Последние полгода он работает в проекте на Redux/React.js (часто их путают с RxJS, но это немного другое).

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

В твоем проекте активно используется реактив. Можешь объяснить, что это такое и с чего это движение началось? Сейчас у всех на слуху RxJS, RxJava, RxPython, RxBasic, ну и разве что RxBrainfuck нет.

Действительно, один из моих предыдущих проектов был насквозь пронизан использованием библиотеки RxJS. Все AJAX-запросы, работа с DOM-событиями, порой просто обработка статичных данных — всё это проходило через RxJS.

Для начала пара слов о «реактивном программировании» как таковом. Например, в жизни с «реактивностью» вы сталкиваетесь в Excel:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Как видите, для автоматического вычисления суммы были созданы ячейки с формулами. На языке реактивного программирования это можно изобразить в виде двух потоков данных, gross и taxes, и третьего потока net, который будет собирать данные из gross и taxes и аггрегировать их по нашей формуле, превращая в итоговое значение.
Я буду пользоваться очень удобным онлайн-редактором, чтобы показывать все свои примеры. Этот редактор хорош тем, что прямо в брузере рендерит результат работы приложения. Ну и самая классная фича радактора в том, что потом все сниппеты останутся доступными по прямой ссылке. Мой первый пример тут.

В интернете можно найти много вариантов определения «реактивного программирования»: в Википедии, здесь, на Хабре. Тема серьёзная, даже существует специальный манифест, который можно подписать, если вы согласны с его идеями — только зачем? Лично мне все эти определения не нравятся, и я придумал свое:

Реактивное программирование — это когда ты вместо обработки событий по одному объединяешь их в поток и затем работаешь уже только с ним.
(Дата, Подпись, Печать).

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Как вообще вы пришли к идее использовать RxJS?

В каждом проекте есть свои особенности, которые влияют на выбор той или иной технологии. Решение может быть обсуловленно сложностью интерфейса, наличием или отсутствием внятного Roadmap’a, который показывает, что проект будет развиваться и усложняться как минимум несколько лет. А ещё в 2012 мы не смогли найти ничего другого, похожего на RxJS. Нам была важна хорошая техническая поддержка, и мы поддались бренду Microsoft.

Зачем же нам понадобился RxJS в тот момент?
Представьте себе приложение, в котором много независимых UI компонентов.
Теперь добавляем сюда штук 10 API запросов (от авторизации до проверки цены продукта). Так, есть.
Затем группируем компонеты во View так, что некоторые используются в разных View, а некоторые — нет. Хорошо получается.

Теперь нам надо научить компоненты менять свое состояние между запросами. Где-то показать слой со спиннером, где-то спрятать данные, когда вернется ошибочный запрос. Причём, компонент может зависить от нескольких API и, более того, иногда имеет значение не одиночный запрос, а серия запросов и их ответы. И вишенку на наш торт — на улице 2012 год, Angular нету, Backbone не подходит — значит, будем работать только с jQuery. Без фреймворка.

RxJS подошел идеально. Мы перестали ловить отдельные события и строить огромные пирамиды callback’ов. Что такое пирамида из callback объяснять не нужно, надеюсь?

Вместо этого между компонентами у нас появились потоки с данными, из которых мы могли создавать новые потоки, нужные в конкретной ситуации. Например, есть у нас поток с выбором какого-то продукта. Если пользователь добавляет что-то в корзину, то в этом потоке появляется событие с добавленным продуктом. Если пользователь удаляет продукт — в потоке появляется событие с пустым массивом. Допустим, мы хотим, чтобы таблица моргала красным каждый раз, когда удаляется что-то из корзины:

Значит, причины были именно такие. Как ты считаешь, а по каким еще причинам имеет смысл начинать использовать реактивный подход в проектах?

В самом начале мы все-все события превращали в Rx.Observable. Со временем мы поняли, что это не всегда оправдано. Например, если мы на 100% уверены, что клик по этой кнопке вызывает перезагрузку страницы, то никаких дополнительных изменений на странице не потребуется. Теперь такие простые и не связанные с другими события мы обрабатываем без RxJS.

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

А это значит, что, разобравшись с асинхронными вещами, мы можем начать использовать RxJS для обработки абсолютно синхронных данных, например, пометить жирным все внешние ссылки:

Ссылка на сниппет.
Резюмируя, скажу, что, как мне кажется, RxJS идеально подойдёт для проектов с большими потоками данных, на которые нужно реагировать — игры, чаты и простые сайты, в которых есть какие-то сложные обновления на странице. Не зря, думаю, RxJS включили в Angular 2.

А какие еще решения есть для JS?

Сходу вспоминается несколько альтернатив. В первую очередь это BaconJS. Библиотека создана примерно в то же самое время, когда и RxJS. Что интересно, в одном из проектов мы даже пытались выбрать между RxJS и Bacon — это было года 4 назад, когда обе библиотеки только-только вышли. Но тогда выбор склонился в сторону RxJS, так как Bacon проигрывал по количеству стандартных методов и более простому API в целом. Ну, и еще одним немаловажным фактором, как я уже сказал, было то, что развитием и поддержкой занимался лишь Juha Paananen, а за RxJS стояла Microsoft. Сегодня пользоваться BaconJS можно без каких-либо опасений, потому что хорошее сообщество уже сформировалось, API хорошо задокументировано, и можно найти много отличных примеров.

Следующая альтернатива — это KefirJS (в языке еще остались слова, к которым не добавили JS? :). Замечательная библиотека для реактивного программированния, поддерживаемая нашим соотечественником Романом Поминовым. Роман создавал KefirJS, стараясь взять простоту API от BaconJS (по сравнению с RxJS) и сразу исправляя ошибки в производительности. И знаете, получилось хорошо! Мы, например, пользуемся кефиром каждый день в одном из проектов.

Неужели всё так радужно? Можо просто взять и использовать эти фреймворки?

Есть нюансы.
Года 3 назад после доклада про RxJS на конференции у меня спросили: «Правильно ли я понял, что для того, чтобы использовать реактивное программирование, нужно изменить образ мышления?» Это был самый правильный вопрос за все мои доклады! Очень часто он читался в глазах слушателей, но задали его только раз. И ответ на него: «Да!»

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

К счастью есть reactivex.io/documentation/operators/switch.html, где можно найти подробное и человеческое описание основных методов и их временных диаграмм, и switch() в том числе:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Получается, что при должном умении и желании вполне можно с этим работать и использовать в продакшн. С чего начать и какие ресурсы можно почитать?

Сегодня ресурсов очень много. Только ресурсов со ссылками на ресурсы десятки, на все вкусы: кто-то любит видео-курсы, кто-то — чтение документации с примерами, кому-то книги подавай. Я бы начал вот с этого списка:

Источник

Основы реактивного программирования с использованием RxJS. Часть 2. Операторы и пайпы

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

В предыдущей статье мы рассмотрели, что такое потоки и с чем их едят. В новой части мы познакомимся с тем, какие методы RxJS предоставляет для создания потоков, что такое операторы, пайпы(pipes) и как с ними работать.

Серия статей «Основы реактивного программирования с использованием RxJS»:

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

Графическое представление потоков

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

Вот как будет выглядеть его графическое представление:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Поток обычно изображается в виде прямой линии. Если поток испускает какое-либо значение, то оно отображается на линии в виде кружка. Прямая черта в отображении — это сигнал завершения потока. Для отображения ошибки используется символ — “×”.

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Потоки в одну строчку

В моей практике я редко сталкивался с необходимостью создавать свои экземпляры Observable напрямую. Большинство методов для создания потоков уже есть в RxJS. Чтобы создать поток, испускающий значения 1 и 2, достаточно лишь использовать метод of:

Метод of принимает на вход любое количество аргументов и возвращает готовый экземпляр Observable. После подписки он испустит полученные значения и завершится:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Если вы хотите представить массив в виде потока, то можно воспользоваться методом from. Метод from в качестве аргумента ожидает любой итерируемый объект(массив, строка и т.д.) или promise, и проецирует этот объект на поток. Вот как будет выглядеть поток, полученный из строки:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

А вот так можно обернуть promise в поток:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Примечание: часто потоки сравнивают с promise. На самом деле, они имеют всего одну общую черту — push стратегию распространения изменений. В остальном это абсолютно разные сущности. Promise не может выдать несколько значений. Он может только выполнить resolve или reject, т.е. иметь только два состояния. Поток же может передавать несколько значений, и может быть повторно использован.

А помните пример с интервалом из первой статьи? Данный поток представляет собой таймер, который отсчитывает время в секундах с момента подписки.

Вот как в одну строчку можно реализовать то же самое:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

И напоследок метод, который позволяет создать поток событий DOM элементов:

В качестве значений этот поток будет получать и испускать объекты события “keyup”.

Пайпы & операторы

Pipe — это метод класса Observable, добавленный в RxJS в версии 5.5. Благодаря ему мы можем строить цепочки операторов для последовательной обработки значений, полученных в потоке. Pipe представляет собой однонаправленный канал, который связывает между собой операторы. Сами операторы являются обычными функциями, описанными в RxJS, которые обрабатывают значения из потока.

Например, они могут преобразовать значение и передать его дальше в поток, или могут выполнять роль фильтров и не пропускать какие-либо значения, если они не соответствуют заданному условию.

Посмотрим на операторы в деле. Умножим каждое значение из потока на 2 с помощью оператора map:

Вот как выглядит поток до применения оператора map:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

После оператора map:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Давайте воспользуемся оператором filter. Этот оператор работает точно так же, как функция filter в классе Array. Первым аргументом метод принимает функцию, в которой описано какое-либо условие. Если значение из потока удовлетворяет условию, то оно пропускается дальше:

И вот как будет выглядеть вся схема нашего потока:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Пишем приложение

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

Требований будет немного:

Начнем с html разметки. Опишем input и ul элементы:

Затем в js или ts файле получим ссылки на текущие элементы используя browser API:

Ещё нам понадобится метод, который будет выполнять запрос к github API. Ниже приведен код функции getUsersRepsFromAPI, которая принимает на вход никнейм пользователя и выполняет ajax запрос, используя fetch. Затем возвращает promise, попутно преобразуя успешный ответ в json:

Следом напишем метод, который будет выводить список названий репозиториев:

Приготовления завершены. Настало время посмотреть на RxJS в действии. Нам необходимо слушать событие keyup нашего input’а. Первым делом мы должны понять, что в реактивном подходе мы работаем с потоками. К счастью, в RxJS уже предусмотрен подобный вариант. Вспомните метод fromEvent, который я упоминал выше. Используем его:

Теперь наше событие представлено как поток. Если мы посмотрим, что выводится в консоль, то увидим объект типа KeyboardEvent. Но нам нужно введенное пользователем значение. Вот тут-то нам и пригодится метод pipe и оператор map:

Перейдем к реализации требований. Начнем с того, что будем выполнять запрос, когда введенное значение содержит более двух символов. Для этого воспользуемся оператором filter:

С первым требованием разобрались. Приступим ко второму. Нам необходимо реализовать debounce. В RxJS есть оператор debounceTime. Данный оператор в качестве первого аргумента принимает число миллисекунд, в течение которых значение будет удерживаться, прежде чем пройдет дальше. При этом каждое новое значение будет сбрасывать таймер. Таким образом, на выходе мы получим последнее значение, после ввода которого прошло 700 миллисекунд.

Вот как может выглядеть наш поток без debounceTime:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

А вот так будет выглядеть тот же поток, пропущенный через этот оператор:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

С debounceTime мы будем реже обращаться к API, за счет чего получим экономию трафика и разгрузим сервер.

В целях дополнительной оптимизации предлагаю использовать еще один оператор — distinctUntilChanged. Данный метод избавит нас от дубликатов. Лучше всего показать его работу на примере:

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

rxjs что это такое. Смотреть фото rxjs что это такое. Смотреть картинку rxjs что это такое. Картинка про rxjs что это такое. Фото rxjs что это такое

Добавим данный оператор сразу после оператора debounceTime. Таким образом, мы не будем обращаться к API, если новое значение по какой-то причине совпадает с предыдущим. Подобная ситуация может возникнуть, когда пользователь ввел новые символы, а затем снова стер их. Так как у нас реализована задержка, в поток попадет только последнее значение, ответ на которое у нас уже есть.

Идем на сервер

Уже сейчас мы можем описать логику запроса и обработки ответа. Пока мы умеем только работать с promise. Поэтому опишем еще один оператор map, который будет вызывать метод getUsersRepsFromAPI. В наблюдателе опишем логику обработки нашего promise:

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

Конечно, мы можем навесить catch на promise в методе next, но из-за этого наш код начнет все больше напоминать “callback hell”. Если вдруг нам понадобится выполнить еще один запрос, то сложность кода возрастет.

Примечание: использование promise в коде с RxJS считается антипаттерном. Promise имеет множество недостатков по сравнению с observable. Его нельзя отменить, и нельзя использовать повторно. Если перед вами стоит выбор, то выбирайте observable. То же самое касается метода toPromise класса Observable. Данный метод был реализован в целях совместимости с библиотеками, которые не могут работать с потоками.

Мы можем использовать метод from, чтобы спроецировать promise на поток, но этот способ чреват дополнительными вызовами метода subscribe, и также приведет к разрастанию и усложнению кода.

Решить эту проблему можно с помощью оператора mergeMap:

Теперь нам не нужно писать логику обработки promise. Метод from сделал из promise поток, а оператор mergeMap обработал его. Если promise выполнится успешно, то вызовется метод next, и наш наблюдатель получит готовый объект. Если же произойдет ошибка, то будет вызван метод error, и наш наблюдатель выведет ошибку в console.

Оператор mergeMap немного отличается от тех операторов, с которыми мы работали ранее, он принадлежит к так называемым Higher Order Observables, о которых я расскажу в следующей статье. Но, забегая вперед, скажу, что метод mergeMap сам подписывается на поток.

Обработка ошибок

Если наш поток получит ошибку, то он завершится. И если попытаться после ошибки взаимодействовать с приложением, то никакой реакции мы не получим, так как наш поток завершился.

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

Мы перехватываем ошибку в catchError и вместо нее возвращаем поток с пустым массивом. Теперь при возникновении ошибки мы очистим список репозиториев. Но затем поток снова завершится.

Все дело в том, что catchError подменяет наш оригинальный поток на новый. И дальше наш наблюдатель слушает только его. Когда поток of испустит пустой массив, будет вызван метод complete.

Чтобы не подменять наш оригинальный поток, вызовем оператор catchError на потоке from внутри оператора mergeMap.

Таким образом, наш оригинальный поток ничего не заметит. Вместо ошибки он получит пустой массив.

Заключение

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

Надеюсь, что данная статья помогла вам лучше понять, как устроен RxJS. Желаю вам успехов в изучении!

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *