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

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

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

Реактивное программирование работает аналогично. Оно имеет дело с потоками данных (например, графиком прибытия автобусов) и распространением изменений (прибытием нового автобуса), позволяя приложениям реагировать в режиме реального времени (так же, как пассажиры реагируют, садясь в автобус). Звучит знакомо?

В этой статье мы углубимся в суть реактивного программирования, сосредоточив внимание на его реализации с использованием JavaScript/TypeScript в среде Node.js. Мы также будем следить за глобальным контекстом, который применим ко многим языкам программирования и платформам.

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

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

Понимание Streams и Observables

Давайте погрузимся в суть реактивного программирования: Streams и Observables. Эти концепции являются строительными блоками реактивных приложений, позволяющими им динамически и реактивно обрабатывать данные. Чтобы понять их значение, давайте вернемся к нашей аналогии с автовокзалом. Представьте себе, что автовокзал оборудован цифровым дисплеем, на котором в режиме реального времени отображаются обновления о прибытии, отправлении и задержке автобусов. На этот дисплей постоянно поступают данные об автобусах — этот поток информации мы называем «streams». Каждый фрагмент новых данных (например, прибытие автобуса) можно рассматривать как «событие» в этом потоке.

Stream: поток данных. В программировании поток — это последовательность текущих данных, доступных с течением времени. Потоками может быть что угодно: движения мыши, нажатия клавиш, твиты или даже обновления фондового рынка в реальном времени. Они не так уж отличаются от цифрового дисплея автовокзала, на который непрерывно поступает информация об автобусах. Короче говоря, поток — это набор значений, перемещаемых во времени, интервал между двумя разными значениями может быть контролируемым (запланированные потоки) или случайным (мы никогда не знаем, когда кто-то отправит нам сообщение, верно?). Потоки могут выдавать три разные вещи: значение (некоторого типа), ошибку или «завершенный» сигнал. Давайте подумаем, например, о системе уведомлений. С одной стороны у нас есть клиент (мобильное приложение, веб-приложение и т. д.), который подписался на группу WhatsApp. Всякий раз, когда в этой группе появляется новое сообщение, приложение реагирует отправкой пользователю push-уведомления, но мы никогда не знаем, когда придут эти сообщения. На рисунке ниже показано, что можно считать потоком. Через некоторое время значение может измениться, уведомляя каждого клиента, подписавшегося на поток, о доступности нового значения. Это дает клиентам возможность отказаться от подписки в любое время.

Как видно на изображении выше, с момента отписки (unsubscribe) клиента он перестает получать новые значения из потока.

Observables: реакция на данные

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

Для иллюстрации рассмотрим цифровой дисплей на автобусной остановке как поток. С нетерпением ожидая и наблюдая за информацией о прибытии вашего автобуса, вы подобны наблюдаемому. Когда отображается прибытие вашего автобуса (событие), вы реагируете, готовясь сесть в него.

Observables характеризуются следующими тремя аспектами:

  • Жизненный цикл данных (Data Lifecycle): наблюдаемый объект — это примитивный тип, который может содержать ноль или несколько значений. Эти значения распространяются на любой временной промежуток, определяя жизненный цикл потока.

  • Отменяемость (Cancellable): Observables можно отменить в любое время. Сообщив производителю, что вам больше не нужны обновления, вы можете отменить подписку на наблюдаемый объект.

    Ленивая оценка (Lazy Evaluation): Observables ленивы, то есть они не выполняют никаких действий, пока вы на них не подпишетесь. Аналогично, они прекращают свою деятельность при отказе от подписки. Это отличается от промисов, которые требуют выполнения и должны быть выполнены каждый раз, когда они вызываются, прежде чем произойдет дальнейшая обработка.

Почему Streams и Observables важны

Streams и Observables имеют решающее значение в реактивном программировании, поскольку они позволяют приложениям обрабатывать данные, которые изменяются со временем — точно так же, как постоянно обновляющаяся информация на дисплее автовокзала.

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

Операторы

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

Например, библиотека RxJS содержит сотни операторов, вдохновленных некоторыми известными методами массивов JavaScript, такими как map, filter, reduce и т. д.

Операторы — это просто функции, которые принимают на вход Observable и его же возвращают с применением к нему некоторой операции.

Давайте рассмотрим две основные операции: mapping и filtering. Взгляните на следующую анимацию:

На рисунке выше для оператора map, когда входной observable выдает значение, оно обрабатывается функцией isEven, и результирующее значение выдается как значение для «выходного» observable.

Для оператора filter, когда входной поток выдает значение, оно передается той же функции, которая выдает значение для observable на выходе, когда оно выполняет условие. В противном случае оно игнорируется. Входные данные являются observable, а оператор возвращает другой observable.

Реактивное программирование на JavaScript/TypeScript и не только

В JavaScript и TypeScript, особенно в среде Node.js, streams и observables обрабатываются одновременно изящно и эффективно.

Node.js предлагает встроенную поддержку потоков, предоставляя мощные возможности обработки данных для серверных приложений. Кроме того, библиотеки и платформы, построенные на основе парадигмы реактивного программирования, такие как RxJS для JavaScript/TypeScript, предоставляют разработчикам мощные инструменты для создания реактивных приложений.

Например, RxJS — это библиотека, специально разработанная для реактивного программирования на JavaScript/TypeScript. Он предоставляет обширную коллекцию операторов для создания, объединения и манипулирования наблюдаемыми. С помощью RxJS разработчики могут легко обрабатывать сложные сценарии потока данных благодаря интуитивно понятному API и обширному набору операторов.

Но реактивное программирование не ограничивается JavaScript/TypeScript и Node.js. Многие другие языки программирования имеют свои собственные реализации парадигм и библиотек реактивного программирования.

Например, в таких языках, как Java, есть RxJava, в Kotlin — RxKotlin, а в Swift — RxSwift. Эти библиотеки предлагают функции, аналогичные RxJS, но адаптированы к соответствующим языковым экосистемам.

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

Краткий итог

Представьте, что мы разрабатываем функцию для нашего приложения автовокзала, которая уведомляет пользователей о приближении их автобуса. Используя RxJS, мы можем создать observable, который представляет поток данных о прибытии автобуса. Каждый раз, когда статус автобуса обновляется (скажем, через 10 минут), observable генерирует событие. Наше приложение может подписаться на эти события (наблюдать за ними) и отреагировать, отправив observable уведомление: «Ваш автобус уже в пути!»

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

Это фундаментальное понимание потоков и наблюдаемых объектов — ваш первый шаг в мир реактивного программирования. Двигаясь вперед, помните о цифровом дисплее автовокзала и о том, как он постоянно обновляется. Наши приложения, как и внимательный путешественник, должны быть готовы максимально эффективно реагировать на эти обновления.

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

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

PS. Данный материал является переводом статьи What is Reactive Programming? Beginner's Guide to Writing Reactive Code

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


  1. nin-jin
    02.01.2025 07:48

    Приведу небольшую цитату из своей будущей статьи:

    В известной работе Kris Kowal " GTOR" реактивность понимается одновременно и более узко (механизм передачи данных между объектами), и более широко (рассматривая любую установку или чтение однократного или множественного значения, как частное проявление реактивности). В таком виде термин "реактивность" можно считать вырожденным, так как под него подходит вообще всё программирование как таковое.

    Также можно встретить дословную трактовку "реактивности", как "программирование на реакциях", подразумевая на самом деле позднее связывание вызываемой стороны с вызывающей, что является одной из форм контроля потока исполнения (control flow). Более корректным термином для такой трактовки является "событийное программирование".

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


  1. markelov69
    02.01.2025 07:48

    С помощью RxJS разработчики могут

    писать только максимально отвратительный, вырвиглазный неподдерживаемый write-only код, это да.


    1. yuriy-bezrukov
      02.01.2025 07:48

      За 10 лет был на проектах и везде прекрасно rxjs работает в связке с angular.

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


      1. nin-jin
        02.01.2025 07:48

        Ага, и поэтому его сейчас стремительно переводят на сигналы.


        1. barmaglot92
          02.01.2025 07:48

          Сигналы отправляются из космоса прямо тебе в голову


        1. yuriy-bezrukov
          02.01.2025 07:48

          Сигналы не замена rxjs, они синхроные...


          1. nin-jin
            02.01.2025 07:48

            Если они до синхронных 10 лет шли, то асинхронные ждём к 2035.


      1. s3kaaZa
        02.01.2025 07:48

        Поддержу. Классная библиотека, если понимаешь чего от неехочешь


      1. markelov69
        02.01.2025 07:48

        За 10 лет был на проектах и везде прекрасно rxjs работает в связке с angular.

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

        Кстати, использование этого стека в корп сегменте с очень большими проектами может что-то сказать в пользу rxjs

        Ясно, у всех hello world, а у angular избранные проекты, поэтому вырвиглаз код это "норма". Расходимся


  1. orefkov
    02.01.2025 07:48

    Так и не понял, что тогда не реактивное программирование...
    То есть если какое-то событие происходит 1000 раз в секунду, то тысячекратная перерисовка интерфейса на 60 герцовом мониторе позволит мне "с лёгкостью обрабатывать огромные массивы данных и создавать адаптивный опыт и что-то там ещё из маркетинговой чуши"?


  1. iShrimp
    02.01.2025 07:48

    Чем оно отличается от событийной концепции со слушателями и обработчиками событий?


    1. Raspy
      02.01.2025 07:48

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

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


  1. Wicirelllis
    02.01.2025 07:48

    "Если вы видели Эксель - это именно оно"


    1. LaRN
      02.01.2025 07:48

      А рассылка сообщений оконным функциям в windows это не оно?


  1. Kealon
    02.01.2025 07:48

    Описывать реактивное программирование с помощью библиотеки, сочетающей несколько парадигм довольно странно.


  1. titan_pc
    02.01.2025 07:48

    Я думал тут будет - заказчик пришёл и попросил фичу А. Через 60 секунд она нужна в продакшн. И тут то и вступает реактивное программирование и супер шустрый ci cd пайплайн, который это доставит. И интеграционные тесты ещё реактивно пробегут