В этом материале мы поделимся с вами переводом интервью с руководителем проекта RxJS 5+, инженером Google Беном Лешем.

В огромном мире фронтенд-разработки существует множество интересных инструментов. Я стремлюсь найти правильный подход к изучению практических вещей, поэтому я решила пообщаться с одним из самых ярких представителей RxJS-сообщества, Беном Лешем. Мне хотелось побольше узнать о RxJS, и о том, почему мне, начинающему разработчику, стоит вложить время в изучение реактивного программирования. Кроме того, мне хотелось понять — зачем применять RxJS в моих проектах. Бен рассказал мне о том, как использовать RxJS и поделился советами, касающимися изучения этой технологии.



Какую проблему решает RxJS?


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

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

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

В противоположность этому, RxJS даёт способ устранения этих ограничений, давая несколько каналов связи, что упрощает обработку многошаговых событий и повышает её эффективность. RxJS, кроме того, даёт разработчику возможность единообразно представлять всё, что нуждается в генерировании событий. Когда всё выглядит одинаково, оказывается, что со всем этим очень просто работать: комбинировать, объединять, выполнять запросы. Всё это делает RxJS очень мощным инструментом.

Зачем изучать RxJS?


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

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

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

Какие преимущества даёт использование RxJS?


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

Как выглядит процесс интеграции наблюдаемых объектов в большую кодовую базу?


Наблюдаемые объекты можно использовать практически в любом приложении. Если речь идёт о командной разработке, это может занять некоторое время, однако, переход можно упростить, если выполняются следующие условия:

  • Работа начинается с применения наблюдаемых объектов, которые просты и целесообразны;
  • Изменения хорошо комментируются;
  • До каждого члена команды доносят смысл выполняемых действий;

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

Существуют ли ситуации, в которых использование наблюдаемых объектов не рекомендуется?


Конечно существуют! Если перед нами отдельное событие, которое выполняет какое-то одно действие, как часто бывает в JS-программировании, тогда использовать RxJS — это малость странновато. Конечно, и тут можно применить RxJS, но это будет явный перебор.

Реализация операции «перетащить и опустить» — отличный пример задачи, для решения которой идеально подходит RxJS. Это — событие со множеством действий, сложность которого, при наличии такой возможности, всегда полезно уменьшить.

Как понять, когда следует использовать наблюдаемые объекты?


Полезно, при принятии решения о том, стоит ли использовать наблюдаемые объекты, опираться на контекст. Например:

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

С чего стоит начать, приняв решение изучить операторы Rx?


Список операторов, с которых стоит начать, должен включать в себя map, filter и scan. Среди других важных операторов можно отметить следующие:

  • switchMap
  • concat
  • share / shareReplay

Вы можете сузить список используемых операторов до менее чем десяти тех, которые требуются чаще всего. Есть, конечно, и операторы, с которыми можно поэкспериментировать, вроде pairwise, bufferCount и groupBy. Эти таинственные операторы существуют не просто так, но пользуются ими не особенно часто. Однако, в тех редких случаях, когда вам понадобится выполнить действия, реализуемые этими операторами, вам не придётся ломать голову над тем, как создать их самостоятельно.

Легко ли начать использовать RxJS при работе с фреймворком вроде React?


Применение RxJS в React очень похоже на его применение в Angular, где он внедрён в полном объёме. В Angular наблюдаемые объекты — это полноправные сущности системы, отсюда идёт и особенная простота использования этой технологии в Angular. Однако, это не сложнее, чем подписка на RxJS в componentDidMount и отписывание в componentWillUnmount. В основе всего этого та же идея, что и в Angular, отличие только в том, что в Angular эти механизмы встроены, а в React придётся всё реализовывать вручную.

Можете ли вы поделиться какими-нибудь советами по отладке Rx?


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

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

Вот несколько советов по отладке:

  • Не скупитесь на вызовы console.log();
  • Включите в цепочку наблюдаемых объектов оператор do, что даст возможность видеть состояние наблюдаемого объекта на разных шагах, и то, что он возвращает.

Что вы можете сказать о будущем RxJS?


В RxJS ожидается появление нескольких изменений, на которые стоит обратить внимание. Так, скоро будут доступны так называемые «арендуемые» (lettable) операторы. Благодаря этой концепции, например, вместо наличия оператора map в самом наблюдаемом объекте, будет иметься функция map, которая, когда её вызывают, возвращает другую функцию. Эту функцию затем будет использовать наблюдаемый объект для выполнения тех же действий, которые выполнялись с помощью его собственного оператора. Например, вместо того, чтобы писать нечто вроде observable.map.filter.scan, можно будет написать observable.compose(map, filter, scan). Это — очень серьёзное и полезное изменение, так как, когда имеются функции для создания других функций — открываются множество возможностей функционального программирования. Ещё одно усовершенствование связанное с этим улучшением, заключается в устранении неиспользуемого кода при сборке пакетов (tree shaking).

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

Как растущая популярность RxJS влияет React и Angular?


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

Создатели Rx — замечательные люди, но они используют сложную терминологию, что привело к недостатку внимания к проекту со стороны сообщества разработчиков. Однако, благодаря образовательным ресурсам, вроде This Dot, сейчас наблюдается повышение интереса к этому замечательному инструменту.

Для того, чтобы способствовать дальнейшему распространению RxJS, сейчас я работаю над уменьшением размера библиотеки. Следите за новостями о Tiny Rx, или T-Rx. Если в двух словах, то этот проект позволил уменьшить 24-килобайтную (сжатую g-zip) библиотеку всего до 3 Кб!

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

Если вы, немного ознакомившись с возможностями RxJS, хотите узнать больше, вот несколько полезных ссылок: Rx Workshop, Intro to Rx, Thinking Reactively и RxJS in Depth.

Уважаемые читатели! Пользуетесь ли вы RxJS в своих проектах?

Комментарии (8)


  1. psFitz
    08.11.2017 15:45

    Как-то ни о чем статья, хотелось бы увидеть примеров как реализуется кусок кода с RxJS и без него, а вы лишь написали что-то вроде: "RxJS это круто, вот вам описание которое есть в гугле, его надо использовать там где надо использовать".


  1. burfee
    08.11.2017 17:28

    За статью спасибо.
    Но RxJS пока кажется дает сомнительные преимущества.
    Хотелось бы больше реальных жизненных примеров, где RxJS существенно упрощает код по сравнению с промисами, для которых уже есть поддержка стандарта языка в т.ч. async/await. Объединять промисы достаточно удобно с Promise.all.
    В Ангуляре натянули Rx поверх http запроса, но он по природе одноразовый и по статье не рекомендуется использовать в этом случае. Документация у RxJS мягко говоря странноватая.
    Получается Rx нужен когда есть асинхронная последовательность действий (или даже много таких последовательностей), которую надо не ласково колбасить по ходу (фильтровать, debounce и пр.). Но много ли таких ситуаций? Сделал вывод, что для применения в общем случае больше за уши притянуто, чем реального профита.


    1. Poccomaxa_zt
      08.11.2017 21:57

      Если есть интерес — могу предложить для примера заглянуть в статью, которую я переводил пару месяцев назад — habrahabr.ru/company/infopulse/blog/338910… Этот пример конечно больше является руководством, которое старается объяснить принцип работы, но оно может также показать, как реактивное программирование может работать при связанных задачах…
      Если говорить к примеру про ангуляр и его связку с rxjs — думаю можно было бы привести к примеру какуе-то задачу по типу таблиц с разнообразным фильтрами для пользователя и реакцией отображения на какие либо действия, асинхронные данные и тд…


  1. kahi4
    08.11.2017 18:23

    По примерам: небольшой пример, в котором заданная строка выводится по букве и может быть остановлено-продолжено по какому-то событию (клику на правую часть). Безусловно, конкретно такой пример на чистом js делается в пару строк через setTimeout, однако с ростом сложности и количества событий, которые могут останавливать-запускать-что-нибудь менять в процессе код поверх setTimeout будет нарастать гораздо быстрее и будет гораздо сложнее.


    А вот пример функции, которая действительно делается на rx в одну строчку, но на чистом js это тот еще гемморой — .retry, которая перевызовет цепочку событий в случае появления исключения (в целом, чаще всего используется для того, чтобы сходить на бэкэнд еще раз если с первого раза не получилось).


    Однако эти все примерчики так, ни о чем по большому счету. Суть RX в изменении парадигмы мышления. Если раньше вы писали что-то типа:


    someHighModelFunction() { // page is loaded for example
        doA();
        doB();
        doC();
    }

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


    someHighModelFunction(page) {
       this.currentPage$.push(page)
    }
    
    /* somewhere in dependencies */
    
    Router.currentPage$.subscribe(doA());
    

    И теперь всегда, когда необходимо расширить количество действий, к которым приведет переключение страницы, нам не нужно будет трогать сам код отображения этой страницы, а только подписаться непосредственно в тех местах, где подписка ему нужна. Эдакие events 2.0.


    А поверх уже для удобства использования существует куча крутых методов вроде switchMap и прочего.


    1. vasIvas
      08.11.2017 18:49

      Само возникновение этой библиотеки в c# и постоянные упоминания самих создателей, свидетельствуют что это не событийная библиотека и использовать её как событийную библиотеку является просто неправильно. Она только для асинхронности. Вот например Вы получили событие от домэлемента но изменение стилей не сработают в этом же обновлении, поэтому делаем задержку и проблема решена. А создана она была вообще для работы с потоками, которых в js вообще нет. Использовать её для получить событие и сделать что-то, просто глупо.


      1. Gentlee
        08.11.2017 21:43

        В том наверное и проблема, что Rx пытается заниматься несколькими вещами одновременно — это тебе и замена Task/Promise, и реализация паттерна Observer, которая очень удобна для связывания View и состояния. Но вместе эти две концепции работают не очень здорово, все таки состоянием лучше управлять явно, и View связывать уже непосредственно с ним, поэтому и используют ее в основном как событийную библиотеку, а DAL пишут используя все таки более удобные и привычные Task/Promise.


        1. vasIvas
          08.11.2017 22:48

          del


  1. artem-galas
    08.11.2017 21:43

    Использую rxJS в проектах на Angular.
    Как-то раз мне нужно было пересчитать, сложный вложенный объект в пяти разных местах после ввода пользователя, все это решилось довольно просто, и работало быстро rxJS.