Библиотека RxJS помогает внедрить функции реактивного программирования и Observables в JavaScript. Если вы похожи на меня, вы, вероятно, впервые услышали об этом, работая с Angular, так как многие функции фреймворка прямо из коробки основаны на Observables.

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

  1. Думайте об Observables как об асинхронных массивах.
  2. Думайте об операторах как о воронках.

Давайте рассмотрим их подробно, чтобы вы также могли начать использовать их сегодня!

1. Думайте об Observables как об асинхронных массивах.

RxJS.dev определяет Observables следующим образом:

«Observables — это ленивые коллекции Push из нескольких значений».

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

К счастью, недавно на встрече Meetup Абрахам Уильямс сделал презентацию по Observables и Angular, в которой он показал небольшую диаграмму, которая превратилась в огромную Ага! момент в моем понимании того, что такое Observables. В нем он обращался к пересечению одно-/многозначных и синхронных/асинхронных переменных. Он сделал это, чтобы указать, где Observables вписываются в картину, разбив ее следующим образом:

Здесь все стало ясно: концептуально Observables можно рассматривать как асинхронные массивы!

Что также заставило вещи щелкать, так это не только то, что они являются многозначными переменными, но и то, как Observables отличаются от Promises. Обещания изначально поддерживаются в JavaScript для обработки одиночных асинхронных значений, поэтому, как только обещание разрешено, оно выполнено. Например, следующий фрагмент создает промис, который возвращает бип при разрешении через одну секунду (затем мы берем эту строку и выводим ее на консоль).

Главное здесь — увидеть, что, как только обещание будет разрешено, больше ничего не будет испущено.

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

Например, приведенный ниже фрагмент кода создает Observable, который выдает три строки («beep», «boop», «bop») перед завершением:

Как видите, первые два значения («beep», «boop») выдаются одно за другим, а третье значение («bop») выдается через десять секунд. В течение этого времени subscription остается «живым», ожидая, пока что-то еще придет в поток.

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

Мы можем добиться этого, используя оператор создания RxJS fromEvent(), который позволяет нам создать Observable, который выдает данные о событии каждый раз, когда запускается событие указанного элемента DOM (в данном случае, document’s MouseClick event):

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

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

Итак, вот оно: Observable подобен массиву асинхронных данных.

2. Думайте об операторах RxJS как о воронках

Еще одна ментальная модель, которая помогла мне лучше понять, как обращаться с Observables, была взята из видео Максимилиана Шварцмюллера из Academind.

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

Основываясь на приведенном выше примере события клика, предположим, что теперь мы хотели бы обнаруживать двойные клики на основе того, как быстро пользователь нажимает кнопку клика (в этом упражнении нажатие более одного раза каждые 250 мс будет считаться двойным кликом). Когда обнаружен двойной щелчок, мы выводим на консоль сообщение «двойной щелчок!». Вот код:

Как видите, мы используем метод pipe() Observable для настройки последовательного, подобного воронке списка операторов, которые помогают нам обрабатывать каждое испускаемое значение. Однако, прежде чем мы углубимся в код, давайте сначала рассмотрим, что делает каждый из перечисленных операторов:

  • bufferTime(): оператор, который «сохраняет» все значения, испускаемые Observable, в течение определенного периода времени, а затем возвращает их в массиве по истечении времени.
  • map(): один из наиболее часто используемых операторов, map() запускается каждый раз, когда Observable выдает значение и позволяет нам что-то делать с этим значением. Он автоматически оборачивает все, что мы возвращаем, в новый Observable, чтобы его можно было продолжать обрабатывать в дальнейшем.
  • filter(): позволяет значениям, испускаемым Observable, продолжать движение вниз по потоку, только если они соответствуют требуемому условию фильтрации.

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

При каждом событии клика:

  1. Используйте bufferTime() для запуска массива событий кликов и подождите еще 250 мс, чтобы увидеть, происходят ли какие-либо другие события кликов, сбрасывая время буфера для каждого клика. Когда через 250 мс больше не будет кликов, верните новый Observable с массивом событий кликов.
  2. Используйте map(), чтобы взять массив значений и вернуть новый Observable только с длиной массива в качестве значения.
  3. Используйте filter(), чтобы значение продолжалось вниз по потоку только в том случае, если длина массива больше или равна 2 (т. е. «двойной щелчок»).
  4. В блоке subscribe() выведите 'double click!' на консоль для каждого события, которое успешно удовлетворяет условию фильтрации.

Как вы можете видеть, в этой реализации мы контролировали поток значений с помощью bufferTime(), изменяли значения так, чтобы с ними было легче работать, с помощью map(), и дополнительно управляли условным потоком значений с помощью filter(), прежде чем достичь блока subscribe(), чтобы сделать то, что мы хотели: печатать сообщение на консоль при каждом двойном щелчке.

Итак: операторы похожи на воронки, которые помогают нам обрабатывать поток значений, испускаемых Observable.

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

Кроме того, эта ментальная модель также помогла мне в общих чертах определить, какие существуют типы операторов:

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

Заключение

Всегда помните об этом: наблюдаемые объекты — это асинхронные массивы, а операторы — это воронки, которые помогают вам контролировать поток потока и изменять его по мере необходимости.

Я надеюсь, что это поможет, и удачного кодирования!

Спасибо Майку Гуйнесу

Создавайте компонуемые веб-приложения

Не создавайте веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых фреймворках, таких как React или Node. Создавайте масштабируемые интерфейсы и серверные части с мощным и приятным опытом разработки.

Перенесите свою команду в Bit Cloud, чтобы совместно размещать и совместно работать над компонентами, а также значительно ускорить, масштабировать и стандартизировать разработку в команде. Начните с компонуемых интерфейсов, таких как Design System или Micro Frontends, или исследуйте компонуемый сервер. Попробуйте →

Узнать больше