<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
  <channel>
    <language>ru</language>
    <title>Реактивное программирование</title>
    <description/>
    <link>https://tproger.ru/tag/reactive</link>
    <atom:link href="https://tproger.ru/tag/reactive/feed" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Wed, 07 Oct 2026 07:17:11 GMT</lastBuildDate>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>12</sy:updateFrequency>
    <image>
      <title>Реактивное программирование</title>
      <link>https://tproger.ru</link>
      <url>https://tproger.ru/apple-touch-icon.png</url>
    </image>
    <item>
      <title>Разработка TypeScript-библиотеки для построения реактивных графов распространения и обработки данных</title>
      <link>https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo</link>
      <comments>https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Сморен Фрилайт]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo</guid>
      <description><![CDATA[<p>Как построить гибкий реактивный граф обработки данных, где узлы изолированы друг от друга, а связи между ними строятся автоматически на основе их возможностей? Рассказываю о разработке Transferum — легковесной TypeScript-библиотеки для реактивных потоков. Внутри: разбор системы вычислимых типов для проверки контрактов в compile-time, управление маршрутизацией данных в графе и примеры построения динамических пайплайнов для IoT-датчиков и панелей управления.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/razrabotka-typescript-biblioteki-dlya-postroeniya-reaktivnyh-grafo">Разработка TypeScript-библиотеки для построения реактивных графов распространения и обработки данных</a>»</p>]]></description>
      <category><![CDATA[Open Source]]></category>
      <category><![CDATA[Функциональное программирование]]></category>
      <category><![CDATA[Архитектура ПО]]></category>
      <category><![CDATA[TypeScript]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Библиотеки]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 23 Jul 2026 15:14:02 GMT</pubDate>
      <content:encoded><![CDATA[<h2>Предыстория</h2><p>Все началось с рабочей задачи по реализации весьма специфичной web-панели мониторинга и управления различным оборудованием, в которой нужно было получать данные и отправлять команды по разнообразным сценариям (периодический опрос, подписка на websocket-события, https-запросы и т.д.), а также выводить состояние и графики в реальном времени, динамически комбинируя различные источники данных в разных виджетах.</p><p>Кроме того, у панели было предусмотрено несколько настраиваемых режимов работы, которые переключались по запросу пользователя, что требовало массового управления маршрутами потоков данных внутри системы.</p><p>В первой итерации с использованием RxJS получилось много неструктурированного и сложно поддерживаемого кода, так как архитектура на RxJS вынуждала императивно описывать перестроение топологии при изменении правил маршрутизации «на лету». В нашем специфичном кейсе с динамическими виджетами это приводило к сайд-эффектам и сложностям в отладке. Кроме того, местами размывалась строгая типизация и мы лишались compile-time гарантий. Так появилась идея разработать собственное решение на основе альтернативной концепции — модели графа потоков данных.</p><p>В результате получившаяся модель продемонстрировала предсказуемое поведение и низкую связность компонентов, а кодовая база сократилась на ~30% и приобрела более декларативный вид. Убедившись в эффективности решения, мы решили оформить его в виде отдельной библиотеки с открытым исходным кодом — <b>Transferum</b>.</p><h2>И что, получилась просто еще одна реактивная библиотека?</h2><p>Не совсем. Классические FRP-библиотеки (RxJS, Bacon, Most) построены вокруг единственного примитива (Observable<i></i>). Transferum же основан на композиции различных типов узлов с явно определенным поведением.</p><p>Каждый узел в графе потоков распространения данных явно декларирует свои способности: может ли он принимать данные через push, отдавать через pull, распространять полученный сигнал подписчикам, опрашивать источник, фильтровать, блокировать поток и т.д.</p><p>Объявленные узлом возможности являются одновременно <b>флагами для использования в runtime</b> и <b>compile-time гарантиями</b> наличия соответствующих методов, определяющих его поведение.</p><p><b>Ключевая идея:</b> поведение системы описывается как композиция независимых возможностей, которые одновременно определяют тип, реализацию и правила взаимодействия.</p><h2>Transferum предоставляет четыре слоя абстракции:</h2><ol><li>Трансферы — узлы графа (каналы, поллеры, мапперы, буферы, разветвители и концентраторы, реализации debounce, throttle, switchMap и т.д.).</li><li>Мосты — ребра графа — управляемые вентили между узлами с динамической маршрутизацией и гейтингом.</li><li>Операторы — stateless-трансформаторы и фильтры данных (используются трансферами, отвечающими за конвертацию данных).</li><li>Билдеры — fluent-конструкторы композитных трансферов из цепочек трансферов-примитивов.</li></ol><p>Концептуальная и архитектурная основа — <b>capability flags system</b>. Каждый трансфер реализует CommunicationContractInterface<i></i> — набор булевых флагов, определяющих его возможности.</p><p>Флаги isPushable, isPullable, isSubscribable, isGate и другие — это не просто свойства объекта. Это метаданные, которые:</p><ul><li>Определяют TypeScript-интерфейс трансфера на этапе компиляции.</li><li>Управляют стратегией связывания с другими трансферами в рантайме (с помощью функции linkTransfers()).</li><li>Обеспечивают совместимость в билдерах без приведений типов.</li></ul><p>Один набор флагов — три потребителя. Это единый источник истины для всей системы.</p><p>Когда флаг равен true, соответствующий метод входит в TypeScript-интерфейс трансфера. Это позволяет предоставлять трансфер пользователю вот так:</p><p>Или вот так:</p><p>Именно в таком формате типов фабрики в библиотеке возвращают трансферы. Например:</p><p>Эта <i>«магия»</i> работает в compile-time благодаря несколько замысловатой системе вычислимых типов:</p><h2>Архитектурные инварианты</h2><h2>1. Трансферы не знают своих соседей</h2><p>Трансфер определяет своё поведение (push, pull, subscribe и др.), но никогда не ссылается и не проверяет класс другого трансфера. Он не знает, что является upstream или downstream — лишь выполняет свой контракт. Пользователь может создать свой трансфер, объявить и реализовать его возможности — и он органично и бесшовно впишется в экосистему.</p><h2>2. Мосты не знают конкретных реализаций</h2><p>Мост инспектирует capability flags, а не имена классов. Нет цепочки instanceof, нет переключения по имени класса. Любой output-трансфер может быть соединен с любым input-трансфером — при условии совместимости их флагов, о чем мы поговорим чуть ниже. Это применимо и к тем узлам, которые еще не существуют и будут созданы пользователем.</p><h2>3. Значение undefined никогда не распространяется</h2><p>В Transferum undefined означает «нет данных», а не «пустое значение». Оно подавляется на уровне внутренней реализации менеджера подписок — подписчики никогда не уведомляются с undefined. При этом для явных маркеров пустых значений можно использовать null. <i>Мы сознательно пошли на этот компромисс, чтобы избежать runtime-оверхеда и сохранить нативную скорость работы на плотных потоках данных.</i></p><h2>Связывание трансферов</h2><p>Функция linkTransfers(lhs, rhs) соединяет output-трансфер (lhs) с input-трансфером (rhs) с автоматическим выбором стратегии связывания на основе возможностей этих трансферов:</p><ul><li>isSubscribable → isPushable (реактивная подписка);</li><li>isPullable → isPollingProxy (активный опрос);</li><li>isSubscribable → isAsyncPushable (реактивная подписка + асинхронный push);</li><li>isAsyncPullable → isAsyncPollingProxy (активный асинхронный опрос асинхронного pull-источника);</li><li>isPullable → isAsyncPollingProxy (активный асинхронный опрос синхронного pull-источника).</li></ul><p><b>Protocol-oriented design:</b> механизм не спрашивает <b>«какой это класс?»</b> — он выясняет, <b>какие у него есть возможности</b>. Любая пара трансферов с совместимыми возможностями является <b>linkable</b>. Добавление нового класса трансфера требует только объявления его флагов и реализации соответствующих методов — как связать его с другим трансфером, связующий алгоритм разберется сам.</p><h2>Sync и async в одной экосистеме</h2><p>Синхронные и асинхронные трансферы сосуществуют и могут быть связаны между собой. linkTransfers() предпочитает sync-связывание, когда это возможно, а async-стратегии применяет только когда sync неприменим. Нет отдельного «асинхронного мира».</p><h2>Поддержка backpressure</h2><p>Ряд асинхронных трансферов (AsyncSinkTransfer, AsyncWriteTransfer, AsyncConvertTransfer, AsyncConditionTransfer) поддерживают необязательные поля в конфигурации: maxConcurrency, bufferSize и onBufferOverflow — для ограничения параллельных async-операций, очереди избыточных данных и graceful-обработки переполнения. По умолчанию — неограниченная обработка, без буферизации.</p><h2>Локальная обработка ошибок</h2><p>Transferum использует единую модель обработки ошибок для всех трансферов. Каждый трансфер, который может столкнуться с runtime-ошибкой, принимает опциональный onError-хэндлер в своей конфигурации.</p><h2>А теперь — к примерам использования</h2><p>Вот так можно просто и декларативно описать опрос и агрегирование данных из нескольких источников:</p><p>А вот как можно организовать динамический роутинг:</p><p>Пример организации игровой механики:</p><h2>Когда имеет смысл попробовать Transferum</h2><p>Библиотека подойдет для:</p><ul><li>TypeScript-first проектов — благодаря максимально строгой типизации и compile-time вычислению доступных методов любого трансфера на основе объявленных у него флагов возможностей.</li><li>Работы с pull-based источниками данных — polling API, датчиков, хранилищ с PollingProxy.</li><li>Смешанных sync/async пайплайнов — в единой модели без ручного преобразования.</li><li>Явного flow control — gates, bridges, selectors для runtime-маршрутизации.</li><li>Game development / IoT — frame-aligned tickers, idle polling, sensor aggregation.</li><li>Устойчивой обработки ошибок — локальная, non-fatal обработка: одна стадия не убивает пайплайн при условии переданного в конфиге обработчика ошибок, ничего не подавляется молча.</li></ul><h2>Результаты и планы</h2><ul><li>Библиотека уже используется в двух наших внутренних проектах и показывает свою эффективность. Код доступен на GitHub под лицензией MIT, библиотека не имеет внешних зависимостей и поставляется с подробной документацией (README + API Reference).</li><li>В одном из проектов граф состоит из ~80 узлов и стабильно обрабатывает несколько сотен событий в секунду без деградации. На основе этих данных в том числе рендерится 3D-сцена в Babylon.js со стабильным фреймрейтом ~60 FPS без микрофризов.</li><li>Тесты библиотеки покрывают не только отдельные трансферы, но и поведение системы в динамике: переподключение мостов, обработку ошибок в длинных асинхронных цепочках, а также разнообразные граничные случаи. Покрытие — 100%.</li><li>В дальнейшем планируем реализовать хуки и утилиты для более удобного и нативного использования Transferum с Vue и React. Если они окажутся в достаточной мере переиспользуемыми, оформим в отдельные пакеты-адаптеры.</li></ul><p>Буду рад, если вы заглянете в <a href="https://github.com/Smoren/transferum-ts" rel="noopener noreferrer nofollow">репозиторий</a>, попробуете библиотеку в деле и поделитесь замечаниями — обратная связь поможет сделать Transferum лучше.</p><p>P. S. Если вы сталкивались с похожими задачами и решили их как-то иначе — буду рад прочитать о вашем опыте в комментариях.</p>]]></content:encoded>
    </item>
    <item>
      <title>Зачем нужно реактивное программирование на Swift?</title>
      <link>https://tproger.ru/translations/zachem-nuzhno-reaktivnoe-programmirovanie-na-swift</link>
      <comments>https://tproger.ru/translations/zachem-nuzhno-reaktivnoe-programmirovanie-na-swift?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Олег Борисенков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/zachem-nuzhno-reaktivnoe-programmirovanie-na-swift</guid>
      <description><![CDATA[<p>В этой статье, автор рассказывает почему реактивное программирование на Swift — это хорошо</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/zachem-nuzhno-reaktivnoe-programmirovanie-na-swift">Зачем нужно реактивное программирование на Swift?</a>»</p>]]></description>
      <category><![CDATA[Мобильная разработка]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Swift]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 14 Jan 2021 14:10:43 GMT</pubDate>
      <content:encoded><![CDATA[<p>Это не статья-туториал по конкретной библиотеке для реактивного программирования на Swift. Вместо этого, автор хочет разобраться, почему и в каких сценариях его стоит использовать. Посмотреть примеры кода вы можете в другом нашем переводе: <a href="https://tproger.ru/translations/reactive-programming/">«Реактивное программирование на реальных примерах»</a>.</p><h2>Улучшение читаемости кода</h2><p>Все знают про кошмарные замыкания в Swift. Из-за особенностей мобильной разработки асинхронные процессы должны вызываться вовремя. Раньше это делали с помощью замыканий. Кто-то из вас может до сих пор использовать для этого замыкания и это нормально.</p><p>Но однажды вы столкнётесь с вложенными замыканиями. И начнётся кошмар. Читать и редактировать вложенные замыкания — больно. Все это понимают.</p><p>И как же реактивное программирование на Swift помогает уменьшить эту боль?</p><p>Вы можете связывать observables в цепочки с помощью функций: map, flatMap и flatMapLatest. Таким образом можно обрабатывать запросы одновременно, не утруждая себя чтением вложенных замыканий.</p><p>Также функции merge, combine и zip дают возможность выполнять несколько запросов в более управляемом и читаемом виде. Вот цитата о важности хорошей читаемости:</p><p>Конечно, соотношение времени потраченного на чтение кода, ко времени потраченному на его написание — примерно 10 к 1. Мы постоянно перечитываем старый код, ведь это часть процесса написания нового. Следовательно, облегчение понимания кода также облегчает и его написание.</p><p>Поэтому очень полезно тратить время на то, чтобы сделать ваш код более понятным и читаемым.</p><p>Это не означает, что вы должны полностью отказаться от использования замыканий. Однако, используя замыкания вместе с реактивной библиотекой по вашему выбору, вы можете сделать свой код более читаемым.</p><h2>Помощь с обработкой событий</h2><p>В общем сценарии iOS-разработки вам нужно реагировать на события из разных частей приложения. Например кто-то изменил своё имя в одном контроллере, и нужно обновить его в другом.</p><p>Для таких случаев есть несколько сценариев: делегаты, notifications, и пары ключ-значение. Каждый из них работает неплохо. Но у реактивного подхода есть, что предложить.</p><p>Во-первых, реактивное программирование может упростить работу с этими сценариями. Всё, что вам нужно, — это создать observer или subject и подписаться на него. Затем, каждый раз, когда observer порождает объект, все подписчики об этом узнают.</p><p>Можно использовать такой синтаксис:</p><p>Видите, всё достаточно просто. Для этого не нужно много кода. Как я говорил ранее, один из признаков отличной кодовой базы — она понятна любому.</p><p>Вы можете подписаться на subject из первого контроллера в любом месте программы. А затем использовать myObject для своих целей. Например, пропустить первые два объекта. Для этого можно использовать функцию skip. И как я уже говорил, вы можете комбинировать observable с помощью функций merge и zip.</p><p>Если вам нужно зацепление, можно использовать протокол и получать через него доступ к subject. Например:</p><h2>Сделать код более модульным</h2><p>Одна из моих любых особенностей реактивного программирования — передача observable в качестве переменных. Например можно передать сетевой запрос в другой объект и выполнить его, когда потребуется. Это позволяет сделать код более читаемым и модульным.</p><p>Допустим, у вас есть контроллеры, которым требуется observer конкретного типа. Вы можете использовать с ними любые сетевые запросы, observer’ы которых порождают объекты нужного типа.</p><p>Я всегда пользуюсь этим, когда мне требуются переиспользуемые контроллеры для отображения данных пользователю. Возьмём приложение, похожее на Instagram, где есть объект поста, и экран, где эти посты отображаются. Вам нужно выводить на одном и том же экране посты одного или другого пользователя. Так как это скорее всего делается с помощью сетевых запросов, вы можете создать запрос как observable и передать его контроллеру. Остальное контроллер сделает сам.</p><p>Это фрагмент кода иллюстрирующий мою основную идею:</p><p>Таким образом, преимущество здесь заключается в модульности и универсальности. Этот один контроллер можно использовать повторно в любое время, когда вам понадобится отобразить список сообщений. И всё, что вам нужно сделать, — это передать ему observer. Остальное сделает контроллер.</p><p>Кроме вышеперечисленных возможностей, реактивное программирование на Swift:</p><ul><li>позволяет эффективно отлаживать код;</li><li>сильно уменьшает количество строк кода;</li><li>при использовании таких библиотек как <a href="https://github.com/ReactiveX/RxSwift">RxSwift</a> или <a href="https://github.com/ReactiveX/RxSwift/tree/main/RxCocoa">RxCocoa</a>, даёт возможность использовать traits для лучшего понимания кода;</li><li>хорошо подходит для архитектуры MVVM;</li><li>позволяет выполнять цепочки запросов парой строк кода.</li></ul>]]></content:encoded>
    </item>
    <item>
      <title>Реактивное программирование простыми словами — объясняют эксперты</title>
      <link>https://tproger.ru/experts/reactive-programming-in-simple-words</link>
      <comments>https://tproger.ru/experts/reactive-programming-in-simple-words?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Никита Прияцелюк]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/experts/reactive-programming-in-simple-words</guid>
      <description><![CDATA[<p>Классический подход предполагает запрос, ожидание ответа и продолжение работы. Эксперты объясняют, чем от него отличается реактивная парадигма.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/experts/reactive-programming-in-simple-words">Реактивное программирование простыми словами — объясняют эксперты</a>»</p>]]></description>
      <category><![CDATA[Основные принципы программирования]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Ответы экспертов]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Fri, 16 Oct 2020 06:57:07 GMT</pubDate>
      <content:encoded><![CDATA[<p>В предыдущих статьях эксперты объяснили нам, что такое <a href="https://tproger.ru/experts/oop-in-simple-words/">объектно-ориентированное</a>, <a href="https://tproger.ru/experts/what-is-dynamic-programming/">динамическое</a>, <a href="https://tproger.ru/experts/imperative-and-declarative-programming/">декларативное и императивное</a> программирование. В этот раз узнали у них, что из себя представляет реактивное программирование.</p><p>Что такое реактивное программирование?</p><p>Классический подход к программированию предполагает выполнение запроса (к базе, сервисам и т.д.), ожидание ответа и продолжение работы. Во время ожидания поток простаивает. Масштабируют такие системы путём увеличения количества потоков, при этом вычисление оптимального количества потоков становится непростой задачей, так как сильно зависит от характера нагрузки, количества и качества ожиданий. И несмотря на это, избежать на 100% простоя ресурсов не удается.</p><p>В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На возникновение этого события реагирует соответствующий обработчик и выполняет свою задачу, опять генерирует событие и общий процесс обработки продолжается. Генерация события и реакция на него происходят асинхронно. Обработчик (их ещё называют акторами) выбирает следующее событие из очереди, обрабатывает и складывает событие в очередь другому обработчику. Если задача заключается в выполнении запроса в базе данных, то актор посылает запрос на сервер. После получения ответа будет сгенерировано событие с результатом запроса и запущен соответствующий актор.</p><p>Всё это позволяет более эффективно занять ресурсы полезной работой и управлять масштабированием. Повышается отзывчивость приложений. Кроме того, реактивное программирование помогает масштабироваться горизонтально.</p><p>Понятие реактивного программирования тесно связано с понятием модели распространения данных, которая бывает двух типов:</p><ol><li>pull-модель.</li><li>push-модель.</li></ol><p>На самом деле, эти модели вполне логичны и естественны, поэтому их проявления можно проследить даже в обычной жизни.</p><p>Допустим, Вася любит быть в курсе всех новостей по хоккею и поэтому периодически посещает тематические сайты в надежде обнаружить интересный контент.</p><p>Его коллеге — Пете — тоже интересен хоккей, но в отличие от Васи, Петя подписался на рассылку новостей и получает уведомления о важных событиях в хоккее.</p><p>С точки зрения программирования, Вася использует pull-модель: периодически просматривает источники данных, в то время как Петя — push-модель: занимается обработкой входящих сообщений.</p><p>Таким образом, реактивное программирование — это стиль написания кода, который упрощает реализацию приложений, основанных на push-модели.</p><p>На практике этот стиль применяется для обработки входящего потока данных, например:</p><ul><li>сообщения от пользователей;</li><li>уведомления об изменении расписания;</li><li>действия пользователя с интерфейсом и т.д</li></ul><p>Реактивное программирование — это подход к разработке ПО, который строится на реагировании на события и на распространении событий. При этом модель реакции на события предполагает возможность простого распространения этих или трансформированных событий далее по системе. Ярким примером реализации реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек в цепочке значения в зависимых ячейках пересчитываются автоматически.</p><p>В целом, идея реактивного программирования призвана упростить создание сложных систем. В сложной большой системе возникает значительное количество разнообразных событий, каждое требует определённого механизма реакции и обработки. При использовании реактивного программирования события объединяются в потоки, а компоненты системы являются обработчиками потока событий (и также в свою очередь могут являться генераторами событий). Таким образом, сколь ни была бы сложна система и сколько бы в ней ни было разнообразных событий, вся система строится по принципу генерации потоков событий и реакции на них. Подход в моделировании сложной системы позволяет обрабатывать каждое событие асинхронно и изолированно от других. Важно понимать при этом, что дизайн реактивной системы предполагает, что любая функциональность в системе реализуется с помощью событий и обработчиков. Благодаря этому достигается уменьшение связанности между компонентами системы, увеличение гибкости, упрощение масштабирования и повышение устойчивости систем.</p><p>Парадигма реактивного программирования включает отслеживание определенных событий и реагирование на них в асинхронных потоках данных. Эта идея предполагает, что существует источник событий и слушатель событий, который реагирует на события источника.</p><p>Суть реактивного программирования можно изобразить разными способами. Например, представим реку, течение которой несет несколько разноцветных объектов (мячей). Допустим, на берегу сидит человек, которому нужно выловить объекты с определенной характеристикой – только зеленые или красные. Если нам требуется запрограммировать подобную ситуацию, то реактивный подход – это то, что поможет нам оперировать потоками данных, получать данные, совершать математические вычисления и т.д. Работа с потоками требует от программиста определенного опыта и понимания, что и как комбинировать, какие операторы подходят для решения задачи.</p><p>Реактивный подход активно используется в Frontend-разработке и мобильной разработке, одним из его популяризаторов является Netflix.</p><p>Само название парадигмы наводит на мысль, что реактивное программирование подразумевает какую-либо реакцию на изменения. Так и есть: реактивное программирование удобно при создании интерфейсов и построении моделей систем, изменяющихся во времени.</p><p>Реальным применением этой парадигмы может стать веб-фреймворк, поддерживающий архитектуру MVC (Model-View-Controller), в котором при изменении модели изменяется поведение пользовательских представлений.</p><p>Предположим, есть модель Пользователь с полями Имя и Фамилия. Добавляем поле Отчество — и во всех местах, будь это форма на сайте или структура базы данных, происходят соответствующие изменения: форма при следующей генерации содержит новое поле, а для БД генерируется миграция.</p><p>В рамках парадигмы чаще всего используют функции обратного вызова (callback) и конструкции асинхронного программирования (конкретные виды зависят от языка). Также здесь задействуют события (events) или потоки (flows). Если в коде есть такие «следы», значит, с большой вероятностью здесь применяется концепция реактивного программирования</p><h3>Итак, что из себя представляет реактивное программирование?</h3><p>В реактивном программировании обработка делится на большое количество небольших задач, выполнение каждой из которых оканчивается неким событием. На событие реагирует обработчик, который выполняет свою задачу и снова генерирует событие. Генерация события и реакция на него происходят асинхронно.Идея реактивного программирования призвана упростить создание и масштабирование сложных систем.Примером реактивного подхода может служить таблица Excel. В ней существует цепочка вычислений, разделённая на несколько ячеек: при изменении значения одной из ячеек значения в зависимых ячейках пересчитываются автоматически.</p><p>Напоминаем, что вы можете <a href="https://docs.google.com/forms/d/e/1FAIpQLSdSanNvlfPRrSyQWfnoGPflSVwO4KctnjOdEKHzuxjCmFX2dA/viewform">задать свой вопрос</a> экспертам, а мы соберём на него ответы, если он окажется интересным. Вопросы, которые уже задавались, можно найти в списке выпусков <a href="https://tproger.ru/experts/">рубрики</a>. Если вы хотите присоединиться к числу экспертов и прислать ответ от вашей компании или лично от вас, то пишите на <a>experts@tproger.ru</a>, мы расскажем, как это сделать.</p>]]></content:encoded>
    </item>
    <item>
      <title>Кейс: реактивный подход в высоконагруженном приложении на примере сервиса для начисления кэшбэка</title>
      <link>https://tproger.ru/articles/microservice-architecture-with-project-reactor</link>
      <comments>https://tproger.ru/articles/microservice-architecture-with-project-reactor?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Типичный программист]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/microservice-architecture-with-project-reactor</guid>
      <description><![CDATA[<p>Команда SimbirSoft описывает микросервисную архитектуру, Project Reactor и реализацию кэшбэка в онлайн-приложении страховой компании.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/microservice-architecture-with-project-reactor">Кейс: реактивный подход в высоконагруженном приложении на примере сервиса для начисления кэшбэка</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Микросервисы]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Thu, 16 Jan 2020 12:14:29 GMT</pubDate>
      <content:encoded><![CDATA[<p>Рассказывает команда SimbirSoft</p><p>Эта статья не ставит своей целью описание фреймворков и архитектуры, поскольку они и так достаточно хорошо задокументированы. Скорее, она предназначена для тех, кто начинает работу с микросервисами и Project Reactor, и описывает основные особенности указанных технологий и то, с чем придётся столкнуться и работать.</p><p>В отличие от монолитной, микросервисная архитектура основана на выделении небольших независимых служб, каждая из которых реализует отдельную бизнес-функцию. Если в монолите всё связано (и в случае отказа одной функции могут «отвалиться» остальные), то микросервисы позволяют обеспечить гибкость и устойчивость системы. Крупные IT-решения могут содержать в своей архитектуре десятки микросервисов, и с каждым из них может работать отдельная независимая команда.</p><h2>Пример</h2><p>Рассмотрим на примере кейса из нашей практики. Страховая компания обратилась к нам для модернизации своего онлайн-приложения, имеющего гибкую микросервисную архитектуру. Перед нами стояла задача реализовать в приложении кэшбэк, то есть начисление пользователю бонусных баллов за покупку страхового полиса.</p><p>На первый взгляд, задача выглядела просто.</p><ol><li>За каждый оплаченный (продлённый) полис начислять пользователю определённый кэшбэк X% через бухгалтерский сервис. Пользователю должна быть доступна информация о поступившем кэшбэке.</li><li>По достижении определённого суммарного кэшбэка автоматически переводить средства клиенту через бухгалтерский сервис. Пользователю должна быть доступна история выплат.</li></ol><p>Основной проект использовал очередь сообщений Kafka в качестве средства обмена информацией между микросервисами, а также в качестве единственного перманентного реплицированного хранилища информации.</p><p>Когда требуется реализация тех или иных функций на микросервисах, всегда нужно держать в голове возможность горизонтального масштабирования. Код должен работать не только в многопоточной среде, но и в случае, когда будут запущены несколько контейнеров с микросервисом.</p><p>Добавим также, что клиентов у такого приложения предположительно неограниченно много. И многие из них — мобильные, т. е. относительно медленные.</p><h2>Blocking vs Non-blocking</h2><p>Если использовать стандартную сервлетную архитектуру, то каждый запрос будет выполняться в отдельном потоке. Такой подход в целом неплох, особенно если учитывать возможности современных серверов, которые могут обрабатывать одновременно по несколько сотен соединений.</p><p>Но, во-первых, не всегда есть возможность приобрести такой сервер. Во-вторых, и это самое главное, что-то всегда может пойти не так. Например возникнут какие-либо задержки бэка или проявится ситуация «retry storm» — когда множество пользователей приложения из-за недоступности функций инициируют повторные попытки запроса со своих устройств. Тогда количество активных соединений и потоков увеличивается. В этом случае кластерные ноды могут попасть в спираль — резервные копии потоков увеличивают нагрузку на сервер и перегружают кластер. Конечно, можно встроить механизмы регулирования, чтобы компенсировать риски и помочь поддержать стабильность во время этих событий, но понятно, что это не панацея. Кроме того, восстановление может быть достаточно долгим и рискованным.</p><p>Асинхронные системы работают по-другому. В них обычно одному ядру соответствует один поток, а цикл запроса-ответа обрабатывается через события и коллбэки. Получается, что там, где мы раньше «платили» за запрос целым потоком, теперь просто добавляется ещё одно сообщение.</p><p>Понятно, что и восстановление после различных задержек бэка или «retry storm» даётся легче — обработать дополнительные сообщения в очереди гораздо проще, чем складировать кучу потоков. И поскольку все запросы асинхронны, упрощается масштабируемость системы. Если мы видим большое количество сообщений в очереди, то мы просто временно создаём дополнительных потребителей.</p><p>Устойчивость системы достигается за счёт самой природы асинхронного подхода. Во-первых, мы, конечно, можем попробовать обработать исключение локально. А во-вторых, и это главное, в асинхронных системах компоненты не блокируются обработкой исключений: ошибка в одном компоненте не влияет на остальные. Более того, если один компонент не справляется с обработкой сообщения, то сообщение может быть обработано другим компонентом, записанным на этот же адрес.</p><p>Примечание Несмотря на впечатляющие преимущества асинхронного подхода, у всего есть своя цена. С использованием асинхронного кода, в первую очередь, приходит сложность разработки и отладки. Там, где мы раньше могли в дебаге поставить брейкпоинт и посмотреть весь список вызовов, в асинхронном решении сделать так не получится. К тому же, переход на асинхронный код не всегда даёт улучшение в производительности.</p><p>В нашем примере у приложения много пользователей, которые осуществляют доступ с мобильных устройств. По этой причине мы решили использовать один из асинхронных фреймворков.</p><p>Согласно требованиям заказчика, реализовать проект нужно было на Java для удобства его дальнейшей поддержки. Распространённых вариантов высокоуровневых абстракций было не так уж и много, поэтому мы остановились на <a href="https://projectreactor.io/docs/core/release/reference/index.html">Project Reactor</a>.</p><h2>Работа с Project Reactor</h2><p>Reactor — реализация спецификации <a href="https://github.com/reactive-streams/reactive-streams-jvm">Reactive Streams</a>, об этом вопросе уже достаточно подробно рассказывали.</p><p>Несмотря на обилие документации по Reactor, всё же отдельно хотелось бы отметить одну из особенностей при работе со стримами Reactor. В Reactor есть 2 основные структуры данных — Flux и Mono. Обе они являются реализациями интерфейса Publisher и представляют собой асинхронный поток элементов, либо единичный асинхронный элемент соответственно.</p><p>Стримы в Reactor внешне очень похожи на стандартные стримы Java по коллекциям (java.util.stream.Stream). В Java, конечно, Stream — это не только и не столько механизм работы с коллекциями. Но тут важно помнить, что Flux — это тем более не коллекция.</p><p>В Java перед началом стрима по коллекции у нас есть все её элементы, мы знаем её размер и т. п. Flux же лучше рассматривать как некую отложенную коллекцию, количество элементов которой на момент выполнения стрима неизвестно.</p><p>И хотя мы можем стандартными средствами сконвертировать Flux в коллекцию, это будет блокирующая операция, не дающая гарантии выполнения последующих элементов. Как правило, за исключением тестов, так делать нельзя, поскольку мы хотим минимизировать количество блокирующих операций и время простоя нашего железа, особенно для операций ввода-вывода.</p><h2>Схема работы</h2><p>Вернёмся к нашему примеру. В начале работы с проектом мы на основе технического задания определяем функциональные блоки, которые впоследствии станут отдельными микросервисами.</p><p>Мы видим, что у приложения есть 3 основных процесса:</p><ol><li>Получение информации извне (полисы, пользователи, оплата) и начисление кэшбэка на её основе. Это будет первый сервис — «Калькулятор».</li><li>Общение с пользователем. Приложение обращается к хранилищу, чтобы по определённому пользователю найти нужный кэшбэк. Это будет второй сервис — «Хранилище».</li><li>Общение с бухгалтерским сервисом. Начисление кэшбэка и выплаты должны быть проведены через этот сервис. Это будет третий сервис — «Бухгалтер».</li></ol><p>Итак, схема довольно простая:</p><figure><img src="https://media.tproger.ru/uploads/2020/01/Diagram_24121.jpg" alt="" /></figure><p>«Калькулятор» вычисляет кэшбэк по каждому полису/пользователю/факту оплаты и отправляет сообщение в отдельную очередь. Сервисы «Хранилище» и «Бухгалтер» читают сообщения из этой очереди. «Хранилище» сохраняет кэшбэк и показывает его пользователю, а в случае достижения минимального порога выплаты — инициирует зачисление средств на карту пользователя. «Бухгалтер» вызывает внешний бухгалтерский сервис для физического начисления бонусов.</p><h2>Организация локального хранилища</h2><p>Особое значение имеет очерёдность этих процессов. Мы видим, что наш «Калькулятор» работает на основе сообщений от внешних сервисов. Возможны ситуации, когда «Калькулятор» на основе одного входящего сообщения не сможет принять решение об отправке. Например, ему нужно проверить 2 внешних топика: полисы и оплату. В этом случае необходимо внутреннее хранилище, которое мы формируем на основе всех внешних сообщений.</p><p>Сравнивая стандартные SQL варианты (PostgreSQL, MySQL) и NoSQL подход, мы в этом проекте в качестве локального хранилища, решили отдать предпочтение MongoDB по нескольким причинам:</p><ol><li>Для mongoDB есть готовый фреймворк по работе с Project Reactor — <a href="http://reactivemongo.org/releases/0.1x/documentation/tutorial/getstarted.html">reactive mongo</a>.</li><li>Малое количество таблиц и связей между ними.</li><li>Простота использования, нет необходимости следить за соответствием моделей таблицам БД.</li></ol><p>И конечно, нам нужно разделить процессы формирования локального хранилища и принятия решения об отправке. Как это сделать, если решение об отправке принимается на основе тех же сообщений, по которым строится внутреннее хранилище? Одним из возможных вариантов является разделение по времени и запуск начисления по внешнему планировщику. Мы остановились на этом способе реализации, простом и понятном.</p><h2>Репроцессинг</h2><p>Для того, чтобы упростить архитектуру приложения и снизить возможные риски, желательно использовать микросервисы stateless, без локальных хранилищ. То есть вне зависимости от того, какая информация на входе, она просто проходит по цепочке стрима.</p><p>Если это невозможно по тем или иным причинам, можно попробовать изолировать в отдельном слое логику работы с состоянием. Иначе говоря, поставить над логикой с состоянием дополнительный уровень абстракции. В этом случае в приложении есть сегмент statefull, но он изолирован, другие части не связаны с состоянием.</p><p>Однако на практике с этим могут возникать сложности. Например, не позволяет архитектура, нет времени или понимания, как это лучше сделать в конкретном проекте, не позволяют требования репроцессинга. При выключении и включении сервиса (и сбросе оффсетов) такой сервис будет повторно выполнять уже сделанные действия. То есть в нашем случае «Калькулятор» будет повторно отбрасывать сообщения о начислении кэшбэка. Более того, даже локальное хранилище не гарантирует правильной работы, поскольку оно не реплицировано и может быть полностью удалено в любой момент вместе с сервисом.</p><p>Один из вариантов решения — использовать специальную очередь отправленных сообщений. Эту очередь мы будем читать и записывать в локальное хранилище на старте сервиса, вместе со всеми остальными внешними сообщениями.</p><h2>Прочие особенности</h2><p>Ещё одна особенность Project Reactor при работе с фронтом заключается в том, что в большинстве случаев нам недостаточно просто получить какое-либо значение. Чаще нам нужно получить значение и затем отслеживать его изменения. Этот вопрос достаточно просто решить с помощью reactive mongo. У хранилища из библиотеки reactive mongo есть методы получения и отслеживания, которые вернут не только требуемое значение, но и все его последующие изменения, если таковые будут.</p><p>Также обратим внимание на сервис «Бухгалтер». Предположим, что этот сервис работает с внешним API по REST или, как в нашем случае, по SOAP. Здесь также действуют требования по репроцессингу, и нужна отдельная очередь истории. Но также возможны и дополнительные требования по устойчивости системы в целом.</p><p>Например, что будет, если внешний API ответит 500 ошибкой? В нашем случае мы можем воспользоваться стандартным механизмом Reactor .retryBackoff() — он попробует отправить сообщение ещё несколько раз, увеличивая задержку между повторными сообщениями. Можно также настроить стрим на отлавливание определённых ошибок и реагировать только на них. Подробнее можно посмотреть <a href="https://blog.trifork.com/2019/03/13/retry-functionality-in-a-reactive-programming-context/">тут</a>.</p><h2>Тестирование</h2><p>Конечно, на создании рабочих модулей проект не заканчивается. Нам нужно проверить его работоспособность, в частности с помощью юнит-тестов. Для модулей на Project Reactor в юнит-тестах используют StepVerifier — это внутренний компонент, который позволяет правильно протестировать функциональность. Документация по StepVerifier легко доступна и полноценна.</p><p>Интеграционные тесты в большинстве случаев предполагают запуск микросервисов в контейнерах, так что при проектировании следует задуматься о полноценном логировании. Если это не сделано, есть риск, что каждый раз придётся долго искать причины падения.</p><p>Проведя модульные и интеграционные тесты, мы убедились, что наше приложение готово к асинхронной работе, горизонтальному масштабированию, устойчиво к неожиданным выключениям, покрыто тестами. В целом в нашем проекте разработка заняла около трёх недель, включая отладку и ревью заказчика.</p><h2>Вывод</h2><p>Для высоконагруженных приложений с большим количеством внешних пользователей мы рекомендуем рассмотреть вариант асинхронной работы с применением реактивного подхода.</p><p>Хотя реактивная реализация не всегда увеличивает быстродействие системы, однако значительно улучшает её масштабируемость и устойчивость.</p><p>Использование Reactor позволяет достаточно просто реализовать асинхронную работу, сделать решение более наглядным и понятным для дальнейшей поддержки. При этом работа с Project Reactor потребует особого внимания при написании кода, а именно при выстраивании стримов Flux и Mono, также необходимо всегда сверяться с документацией и проводить промежуточные тесты.</p><p>В этой статье мы рассмотрели асинхронную работу, репроцессинг, вызов внешних сервисов, организацию хранилищ, тестирование и некоторые другие особенности, которые важно учитывать в проектах с микросервисами и Reactor. Надеемся, что наш опыт был вам полезен.</p>]]></content:encoded>
    </item>
    <item>
      <title>Как мы на JFuture 2019 ходили: обзор большой JVM-конференции</title>
      <link>https://tproger.ru/articles/jfuture-2019-review</link>
      <comments>https://tproger.ru/articles/jfuture-2019-review?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Тимур Кондратьев]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/jfuture-2019-review</guid>
      <description><![CDATA[<p>Вторая конференция для адептов JVM-языков прошла в минском кинотеатре: как выбиралась локация, откуда выросло мероприятие и что там было.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/jfuture-2019-review">Как мы на JFuture 2019 ходили: обзор большой JVM-конференции</a>»</p>]]></description>
      <category><![CDATA[Java]]></category>
      <category><![CDATA[Веб-разработка]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 10 Dec 2019 10:10:49 GMT</pubDate>
      <content:encoded><![CDATA[<p>16 ноября в Минске прошла вторая по счёту конференция для всех адептов JVM-языков — JFuture 2019. Сотрудники редакции Tproger посетили её и приготовили обзор.</p><h2>Место проведения и немного истории</h2><p>В этом году JFuture прошла в VOKA Cinema by Silver Screen — одном из самых больших мультиплексов Минска. Такое неочевидное место выбрано не случайно: всё дело в самой концепции мероприятия.</p><p>JFuture родилась из международной конференции Java Day, проводимой при поддержке Oracle во многих городах мира. Java Day проводилась на регулярной основе, но в один прекрасный момент организаторам наскучили серьёзные академические доклады о спецификациях, многопоточности и проблемах с памятью, и они захотели сделать что-то самобытное и живое. Так, на стыке JVM, науки и искусства родилась конференция JFuture. Впервые она прошла в 2018 году в одном из старейших театров Беларуси — Национальном Купаловском театре. И, конечно, логичным развитием места проведения стал теперь уже кинотеатр, причём самый технологичный из существующих в Минске.</p><p>К тому же, где ещё вы сможете послушать лекции по Java с попкорном, в удобных креслах и на большом экране?</p><figure><img src="https://media.tproger.ru/uploads/2019/12/76688974_468414394025060_3413123189151105024_o.jpg" alt="" /></figure><h2>Организация</h2><p>На входе нас встретили дружелюбные волонтёры, которые рассказали о некоторых организационных моментах и ответили на вопросы. Уже потом мы выяснили, что за прошедшие сутки они спали всего пару часов.</p><figure><img src="https://media.tproger.ru/uploads/2019/12/76702425_468414757358357_1566618720267141120_o.jpg" alt="" /></figure><p>Проходы были узкими, но столпотворений не возникало благодаря грамотному расположению стендов с активностями и других важных точек. Более того, из-за камерности площадки в коридорах можно было запросто встретить кого-нибудь из спикеров и поговорить по душам, задать интересующие вопросы или просто вместе выпить кофе.</p><h2>Доклады</h2><p>С приветственным словом публику встретила главный организатор конференции Дарья Милько из SPACE_. Она поздравила участников с открытием и рассказала, как пройдёт мероприятие, а затем дала слово первому спикеру. Кстати, выступала Дарья, как и остальные спикеры, на английском.</p><figure><img src="https://media.tproger.ru/uploads/2019/12/76695232_463551941177972_3306728370963742720_n.jpg" alt="" /></figure><p>Конференция прошла в два потока, с полным списком докладов можно ознакомиться на странице JFuture. Мы расскажем только о тех выступлениях, которые смогли посетить.</p><h3>Uncovering Project Amber</h3><p>Открыла конференцию Мала Гупта, Developer Advocate из JetBrains, с докладом о <a href="https://openjdk.java.net/projects/amber/">проекте Amber</a>. Она начала своё выступление с введения в саму инициативу, её целях и методах улучшения Java. Затем спикер описала изменения, уже вошедшие в стандарт языка благодаря усилиям проекта: и вывод типов локальных переменных с помощью var, и switch-выражения, представленные в Java 13.</p><p>После началось самое интересное — Мала рассказала о новых фичах и возможностях, которые только планируется внедрить в Java. Среди них:</p><ul><li>sealed типы и новое ключевое слово Record, позволяющее избавить разработчика от написания boilerplate-кода типа геттеров, сеттеров, методов toString() и equals();</li><li>перечисления с внутренними переменными и даже generic-перечисления;</li><li>«сопоставление с образцом» для упрощения языковых конструкций вида if (obj instanceof Obj) {...};</li><li>однострочные методы с синтаксисом, заимствованным у лямбда-функций.</li></ul><p>В заключении Мала сделала важный вывод: «Изменения постоянны — просто примите это». Ведь языки программирования, как и любые другие, постоянно видоизменяются и улучшаются под стать окружающему миру.</p><p>Первый доклад задал тон всей конференции — все её участники настроились на изучение новых подходов, технологий и способов создавать более эффективные решения.</p><h3>Efficient web apps with Spring Boot 2</h3><p>Стефан Николл из Pivotal затронул очень болезненную для многих Java-разработчиков тему повышения производительности и скорости работы веб-приложений. Безусловно, оптимизировать можно очень долго: рефакторить код, находить узкие места в сетевой инфраструктуре и ускорять работу баз данных. Но так ли это нужно, если в большинстве случаев выигрыш в производительности не стоит всех затраченных на улучшение этой самой производительности средств? К такому выводу и пришёл спикер, представив публике быстрый и эффективный способ оптимизации с помощью <a href="https://projectreactor.io/">Project Reactor</a>. Это кроссплатформенный проект, предоставляющий API для работы с реактивными стримами.</p><p>Стефан много кодил сам, попутно объясняя, что значит та или иная строчка кода, и показывал происходящие с приложением изменения в режиме реального времени с помощью софта для метрик Prometheus. В конце доклада ему удалось значительно уменьшить задержки в отдельных запросах и продемонстрировать полностью рабочий прототип приложения на Spring WebFlux. Конечно, не обошлось и без ограничений реактивного стека: например при использовании WebFlux все микросервисы и драйверы должны быть реактивными. Это значит, что вы не сможете пользоваться Spring Data JDBC или JPA.</p><h3>JVMs in Containers: Best Practices</h3><p>Следующий посещённый нами доклад дал качественную вводную о лучших практиках работы с проектами на JVM-платформе в контейнерах.</p><figure><img src="https://media.tproger.ru/uploads/2019/12/74318416_463633437836489_3929858237905502208_n.jpg" alt="" /></figure><p>Несмотря на хардкорную техническую составляющую большинства докладов, спикеры держались непринуждённо и старались стать ближе к аудитории. Вот Дэвид Делабассе из Oracle и начал выступление с дисклеймера: разработчик не преминул пожаловаться на клавиатуру новых MacBook — в ней то и дело залипали клавиши. Когда все отшутились и посетовали на проблемные лэптопы, спикер рассказал о разнице между контейнерами и виртуальными машинами:</p><figure><img src="https://media.tproger.ru/uploads/2019/12/02DB66F8-0AAF-46F5-8837-F3B456C8ADA1.jpeg" alt="" /></figure><p>Когда с ликбезом было покончено, началась самая интересная часть выступления — живой кодинг. Дэвид начал со сборки контейнера с банальным «Hello, world», однако тут же возникла неожиданность — образ «весил» больше 300 Мбайт.<br />Конечно, сделано это было не просто так. Спикер показал зрителям, как с помощью нехитрых манипуляций с рабочей версией JDK и флагами сборки Docker-файла получилось уменьшить размер более, чем в 15 раз — до 17 Мбайт!</p><p>В конце выступления спикер отметил, что для работы с контейнерами и JVM определённо больше подходят дистрибутивы Linux или MacOS. Кроме того, он остановился на преимуществах <a href="https://www.graalvm.org/">GraalVM</a>, который упрощает и ускоряет сборку проектов.</p><h3>The State Of Reactive Streams</h3><p>Олег Докука, евангелист реактивных стримов, провёл экскурсию в историю реактивного программирования: от зарождения концепции в 1970-х годах в работах сотрудников Microsoft до настоящего времени.</p><p>Доклад был насыщен примерами кода и интересными визуализациями, с помощью которых можно было проследить за развитием различных подходов к обработке потоков данных.</p><p>Кроме насыщенной программы и харизматичного докладчика внимание цеплял ещё и рисованный гусь, который «жил» на просторах презентации:</p><figure><img src="https://media.tproger.ru/uploads/2019/12/B42BAE64-55DF-4A2A-BFF9-CCEEE6B7C509.jpeg" alt="" /></figure><p>Благодаря юмористическим вставкам и миниатюрам с его участием информация воспринималась намного проще.</p><h3>Live Coding Music 101</h3><p>Заключительным стало выступление Петра Ягельского из TouK. Он показал, как с помощью вашего любимого языка программирования и MIDI-контроллера можно играть настоящую электронную музыку. И не просто показал, а сыграл несколько электронных композиций «на лету».</p><p>Пётр рассказал о самой концепции Live Coding Music и ударился в технические детали синтеза звуков с помощью цифровых сигналов и специализированного ПО. Кроме того, спикер подсказал ресурсы, на которых можно найти бесплатные музыкальные сэмплы.</p><figure><img src="https://media.tproger.ru/uploads/2019/12/74209224_463838104482689_2264577165297188864_n.jpg" alt="" /></figure><p>Выступление отчасти смазала общая возбуждённость аудитории — после финального доклада организаторы пообещали разыграть призы — и заметное волнение спикера. Однако это не помешало послушать электронную музыку и узнать о таком необычном направлении.</p><h2>Другие активности</h2><p>Конечно, что за конференция без стендов с партнёрами и клёвыми ништяками?</p><figure><img src="https://media.tproger.ru/uploads/2019/12/74643706_468414577358375_8801586053451350016_o.jpg" alt="" /></figure><p>На площадке JFuture расположились минские и международные компании Kyriba, Playtika, ISsoft, EIS Group и JetBrains. Их представители были готовы пообщаться и позадавать интересные вопросы и задачки, за решение которых можно было получить приятные подарки. Например термокружки, фрисби, йо-йо и наборы значков. И, конечно, тонны стикеров для лэптопа (кажется, скоро придётся завести специальный альбом для них, потому что место на крышке стремительно заканчивается). А Kyriba даже разработала онлайн-квест с вопросами по разным сферам IT, алгоритмам и языкам программирования. За победу в нём можно было выиграть футболку и принять участие в розыгрыше.</p><p>Кроме технических задачек Playtika, Kyriba и ISSoft запустили розыгрыш ценных призов, который шёл в течение всего дня. Для участия нужно было либо просто оставить свои данные, либо, в случае с Kyriba, заработать определённое количество баллов в онлайн-квесте.<br />Призы были действительно стоящими: PS4 Pro, SSD на 1 Тбайт и автономный робот.</p><h2>Заключение</h2><p>Организаторам из SPACE_ удалось выйти на уровень топовых международных конференций и привезти в Минск действительно интересных и выдающихся спикеров. Кроме всего прочего, плюсом конференции стала её камерность, за счёт чего все участники могли свободно пообщаться и поговорить о наболевшем со специалистами в непринуждённой обстановке.</p><p>Конференции и митапы — это всегда отличная возможность быть в курсе того, чем живёт IT-сообщество, узнать о новых техниках и практиках, а также завести новых знакомых и партнёров. Тем более когда в ваш город приезжают именитые разработчики и евангелисты технологий со всего мира.</p><p>Кстати, участие в IT-мероприятиях — это плюс не только для вас, но и для вашего начальства. Ведь специалист, который развивается и живёт, постоянно обучаясь, всегда на хорошем счету. Следите за нашими анонсами в <a href="https://tproger.ru/events/">разделе событий</a> и приобщайтесь к международному сообществу программистов!</p>]]></content:encoded>
    </item>
    <item>
      <title>Реактивное программирование на реальных примерах: подробное введение</title>
      <link>https://tproger.ru/translations/reactive-programming</link>
      <comments>https://tproger.ru/translations/reactive-programming?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Антон Корольков]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/translations/reactive-programming</guid>
      <description><![CDATA[<p>Материал для новичков, которым не хватает внятных объяснений: как начать мыслить реактивно и спроектировать архитектуру проекта целиком.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/translations/reactive-programming">Реактивное программирование на реальных примерах: подробное введение</a>»</p>]]></description>
      <category><![CDATA[Обучение программированию]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Переводы]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Mon, 13 Mar 2017 15:16:45 GMT</pubDate>
      <content:encoded><![CDATA[<p>Обучение реактивному подходу в программировании — достаточно непростая вещь, и недостаток обучающих материалов только усугубляет этот процесс. Большинство существующих обучающих пособий не дают глубокого обзора и не рассказывают о том, как спроектировать архитектуру проекта в целом.</p><p>Этот материал направлен на то, чтобы помочь новичкам начать думать по-настоящему “реактивно”.</p><h3>Так что же такое реактивное программирование?</h3><p>Есть множество не до конца верных определений и объяснений в интернете. <a href="https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B0%D0%BA%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5">Википедия</a> дает слишком скупое описание. Ответы на <a href="http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming">Stack Overflow</a> часто непонятны новичкам. <a href="http://www.reactivemanifesto.org/">Реактивный Манифест</a> выглядит так, будто его писали для руководителей проектов или бизнесменов. Rx терминология от Microsoft, гласящая о том, что “Rx = Observables + LINQ + Schedulers”, звучит настолько тяжело и по-майкрософтовски, что большинство из нас слабо понимает, о чем идёт речь. Такие термины, как “реактивность” и “распространение изменений” не выражают ничего, что бы отличалось от обычного MV* подхода, реализованного уже на бесчисленном множестве языков. Любой фреймворк реагирует на изменения моделей. В любом фреймворке изменения распространяются. Если бы это было не так, пользователь не видел бы никаких изменений.</p><p>Дадим подробное объяснение термину “реактивное программирование”.</p><p>Реактивное программирование — программирование с асинхронными потоками данных</p><p>Впрочем, ничего нового. Event bus’ы или обычные события клика — это тоже асинхронные потоки данных, которые вы можете прослушивать, чтобы реагировать какими-либо действиями. Реактивность — это та же самая идея, возведенная в абсолют. Вы можете создавать потоки данных не только из событий наведения или кликания мышью. Потоком может быть что угодно: переменные, пользовательский ввод, свойства, кэш, структуры данных и т.п. Например, представьте, что ваша лента новостей в Твиттере — поток событий. Вы можете слушать этот поток и реагировать на события соответственно.</p><p>Кроме этого, вы получаете удивительный набор функций для комбинирования, создания и фильтрации этих потоков. Вот где проявляется вся магия этого подхода. Один или несколько потоков могут использоваться как входные данные для другого потока. Вы можете объединять два потока. Также вы можете фильтровать поток, выбирая только те события, которые вам интересны.</p><p>Так как потоки — основопологающая вещь в реактивном подходе, давайте рассмотрим их подробнее на примере пользовательского клика мышью:</p><figure><img src="https://media.tproger.ru/uploads/2017/02/687474703a2f2f692e696d6775722e636f6d2f634c344d4f73532e706e67.png" alt="" /></figure><p>Поток — это последовательность событий, упорядоченная по времени. Он может выбрасывать три типа данных: значение (определенного типа), ошибку или сигнал завершения. Сигнал завершения распространяется, когда текущее окно или окно, содержащее кнопку, закрывается.</p><p>Мы перехватываем эти события асинхронно, указывая одну функцию, которая будет вызываться, когда выброшено значение, другую для ошибок и третью для обработки сигнала завершения. В некоторых случаях можно опустить последние две и сфокусироваться на объявлении функции для перехвата значений. Прослушивание потока называется подпиской (subscribing). Функции, которые мы объявляем, называются наблюдателями (observer). Поток — это объект наших наблюдений (observable, наблюдаемый объект). Это в точности паттерн проектирования, называемый “<a href="https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)">Наблюдатель</a>“. Подробнее о шаблонах проектирования для новичков, читайте в <a href="https://tproger.ru/translations/design-patterns-for-beginners/">нашей статье</a>.</p><p>В данном руководстве мы будем использовать альтернативный способ представления вышеупомянутой диаграммы с помощью ASCII символов:</p><p>Теперь давайте сгенерируем новые потоки сообщений клика, трансформированные из оригинального потока.</p><p>Для начала сделаем поток счетчиков, который определяет, сколько раз кнопка была нажата. В большинстве реактивных библиотек у каждого потока есть множество встроенных функций, таких как map, filter, scan и т.д. Когда вы вызываете одну из этих функций, например clickStream.map(f), она возвращает новый поток, основанный на родительском. Функции не модифицируют родительский поток. Это называется <a href="https://tproger.ru/translations/functional-sharp-1/">неизменяемостью</a> и является неотъемлемой частью реактивного подхода, позволяя нам вызывать цепочку функций, например clickStream.map(f).scan(g):</p><p>Функция map(f) заменяет каждое полученное значение в соответствии с вашей реализацией функции f. В нашем случае функция map производит значение “1” после каждого клика. Функция scan(g) аггрегирует все предыдущие значения, производя значение x = g(accumulated, current), где g в данном случае — это просто функция сложения. В конечном итоге counterStream выбрасывает общее количество кликов.</p><p>Чтобы вы поняли всю мощь реактивного подхода, давайте предположим, что вы хотите реализовать поток событий “двойной клик”. Чтобы сделать эту задачу еще интереснее новый поток должен принимать множественные нажатия за двойные. Представьте себе, как бы вы реализовывали эту задачу в императивном стиле. Потребовалось бы несколько переменных, хранящих состояние, и использование интервалов.</p><p>В реактивном же подходе все достаточно просто. Всю описанную выше логику можно реализовать <a href="http://jsfiddle.net/staltz/4gGgs/27/">четырьмя строками кода</a>. Но давайте пока не останавливаться на коде. Лучший способ научиться понимать и проектировать потоки, вне зависимости от того, новичок вы или эксперт — это изображать диаграммы:</p><figure><img src="https://media.tproger.ru/uploads/2017/02/687474703a2f2f692e696d6775722e636f6d2f484d47574e4f352e706e67.png" alt="" /></figure><p>Серые прямоугольники — это функции, которые трансформируют один поток в другой. Сначала мы собираем клики в списки. Если прошло 250 миллисекунд без единого нажатия кнопки — мы применяем функцию map() на каждом из списков, чтобы вычислить его длину. В конце мы фильтруем списки с длиной 1, используя функцию filter(x &gt;= 2). Вот так, в три действия, мы получаем результат — поток событий множественных кликов. Мы можем подписаться на него и использовать, как пожелаем.</p><p>Этот пример показывает всю простоту, с которой реализовывается достаточно сложная на первый взгляд задача, если мы используем реактивный подход.</p><h3>Для чего нужно реактивное программирование</h3><p>Реактивный подход повышает уровень абстракции вашего кода и вы можете сконцентрироваться на взаимосвязи событий, которые определяют бизнес-логику, вместо того, чтобы постоянно поддерживать код с большим количеством деталей реализации. Код в реактивном программировании, вероятно, будет короче.</p><p>Преимущество более заметно в современных веб- и мобильных приложениях, которые работают с большим количеством разнообразных UI-событий. 10 лет назад всё взаимодействие с веб-страницей сводилось к отправке больших форм на сервер и выполнении простого рендеринга в клиентской части. Сейчас приложения более сложны: изменение одного поля может повлечь за собой автоматическое сохранение данных на сервере, информация о новом “лайке” должна отправиться другим подключенным пользователям и т.д.</p><p>Реактивное программирование очень хорошо подходит для обработки большого количества разнообразных событий.</p><h3>Начинаем думать в реактивном стиле</h3><p>В последующих примерах используется JavaScript и <a href="https://github.com/Reactive-Extensions/RxJS">RxJS</a>, но <a href="http://www.reactivex.io/">Rx-библиотеки</a> доступны для многих других языков и платформ (.NET, <a href="https://github.com/Netflix/RxJava">Java</a>, <a href="https://web.archive.org/web/20140601213843/https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-scala">Scala</a>, <a href="https://web.archive.org/web/20130821051016/https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-clojure">Clojure</a>, <a href="https://github.com/Reactive-Extensions/RxJS">JavaScript</a>, <a href="https://github.com/Reactive-Extensions/Rx.rb">Ruby</a>, <a href="https://github.com/Reactive-Extensions/RxPy">Python</a>, <a href="https://github.com/Reactive-Extensions/RxCpp">C++</a>, <a href="https://github.com/ReactiveCocoa/ReactiveCocoa">Objective-C/Cocoa</a>, <a href="https://web.archive.org/web/20130821053801/https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-groovy">Groovy</a>, и т.д.). На нашем сайте есть руководства по использованию библиотек <a href="https://tproger.ru/articles/rxswift-3/">RxSwift</a> и <a href="https://tproger.ru/articles/reactivex-python/">ReactiveX в Python</a>.</p><h4>Реализуем виджет “На кого подписаться”</h4><p>В Twitter есть такой виджет, который предлагает вам другие аккаунты, на которые вы можете подписаться:</p><figure><img src="https://media.tproger.ru/uploads/2017/02/687474703a2f2f692e696d6775722e636f6d2f65416c4e62306a2e706e67.png" alt="" /></figure><p>Мы намерены реализовать его основную функциональность:</p><ul><li>Загрузка из API и вывод трех аккаунтов;</li><li>По клику кнопки “Обновить” вывод других трех аккаунтов;</li><li>По клику кнопки “x” рядом с аккаунтом — удаление его из виджета и вывод другого аккаунта;</li><li>Отображение аватарки и ссылки на аккаунт в каждой из трех строк.</li></ul><p>Вместо Twitter-аккаунтов, которые закрыты для неавторизованных пользователей, мы будем использовать Github API и брать аккаунты оттуда. Ссылку на Github API для получения списка пользователей вы можете найти <a href="https://developer.github.com/v3/users/#get-all-users">в официальной документации</a>. Также можете смотреть на <a href="http://jsfiddle.net/staltz/8jFJH/48/">готовый код данного примера</a>.</p><h4>Запрос и ответ</h4><p>Как подойти к решению этой проблемы в Rx-стиле? Надо начать с того, что (почти) все, что угодно может быть потоком. Первое, что мы реализуем, будет “Загрузка из API и вывод трех аккаунтов”. Ничего необычного, нужно просто (1) сделать запрос, (2) получить ответ, (3) отобразить ответ. Представим запрос в качестве потока.</p><p>При инициализации мы должны сделать только один запрос, так что если мы смоделируем его, как поток данных, он будет выбрасывать только одно значение. В дальнейшем мы будем делать множество запросов, но на данный момент нам нужен только один.</p><p>Когда происходит запрос, он сообщает нам две вещи: когда запрос должен быть выполнен — время генерации события, и куда мы делаем запрос — значение генерируемого событие, строка, содержащая URL.</p><p>Создать поток, содержащий одно значение, очень просто с библиотеками семейства Rx*:</p><p>То, что мы написали — это просто поток, содержащий строку, который не делает ничего, так что мы должны как-то заставить его действовать так, как нам нужно. Это делается с помощью <a href="https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypesubscribeobserver--onnext-onerror-oncompleted">подписки</a> на поток:</p><p>Заметьте, что мы используем Ajax-коллбэк (callback) из библиотеки jQuery, чтобы управлять асинхронностью операции запроса. Если вы слабо понимаете, что такое callback’и, почитайте нашу <a href="https://tproger.ru/translations/asynchronous-javascript/">статью об эволюции асинхронного программирования в JS</a>. “Но подождите, Rx же работает с асинхронными потоками данных. Не может ли ответ на запрос быть потоком, содержащим данные, которые придут когда-нибудь позже?” — можете спросить вы. Что ж, на концептуальном уровне все верно, давайте попробуем это реализовать:</p><p>Rx.Observable.create() создает пользовательский поток данных, информируя каждого подписчика о событиях (onNext()) или ошибках (onError()). Мы обернули Ajax-промис в соответствующий коллбэк. Значит ли это, что Promise — то же самое, что Observable? Да, значит. Подробнее о Promis’ах читайте в <a href="https://tproger.ru/translations/meet-the-promises/">нашей вводной статье</a>.</p><p>Observable — это Promise++. В Rx вы можете конвертировать Promise в Observable очень простым образом:</p><p>Единственное отличие между Promise и Observable в том, что Observable не совместим с <a href="http://promises-aplus.github.io/promises-spec/">Promises/A+</a>. Promise — это, по сути, Observable с одним генерируемым значением. Потоки в Rx расширяют промисы, позволяя возвращать множество значений.</p><p>Возвращаясь к нашему примеру: вы можете заметить, что мы вызываем функцию subscribe() два раза — один внутри другого. Также создание responseStream зависит от requestStream. Как было сказано выше, в Rx есть простые механизмы, позволяющие трансформировать и создавать новые потоки из других, так что мы должны этим воспользоваться.</p><p>Одна из таких функций, с которой вы уже познакомились — map(f) — берет каждое значение из потока A, применяет на нем f() и производит значение для потока B. Если мы применим эту функцию на потоке запроса и ответа, мы можем преобразовать список URL’ов в промисы ответа.</p><p>Затем мы должны создать поток потоков, называемый так же метапоток (metastream). Не пугайтесь, все достаточно просто. Метапоток — это такой поток, в котором каждое генерируемое им значение является потоком. Можно представить их себе как указатели: каждое генерируемое значение — указатель на новый поток. В нашем примере URL каждого запроса преобразуется в указатель на поток, содержащий промис ответа.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/687474703a2f2f692e696d6775722e636f6d2f48486e6d6c61632e706e67.png" alt="" /></figure><p>“Но зачем же нам обернутые в потоки ответы?” — можете спросить вы. В данном случае можно преобразовать метапоток в обычный поток ответов сервера, в котором каждое генерируемое значение является JSON-объектом, а не промисом, с помощью функции flatmap(). Но, тем не менее, метапотоки — обычное явление в реактивном программировании, и в частности, они используются для обработки асинхронных запросов.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/687474703a2f2f692e696d6775722e636f6d2f4869337a4e7a4a2e706e67.png" alt="" /></figure><p>Как и ожидалось, если у нас впоследствии будут какие-то события, генерируемые потоком запросов, поток ответов будет реагировать на них соответствующим образом:</p><p>И теперь, когда у нас есть поток ответов, мы можем отрендерить получаемые данные:</p><p>Весь код целиком:</p><h4>Кнопка обновления</h4><p>Нужно отметить, что список пользователей, который мы получаем по API, состоит из 100 элементов. API позволяет нам задавать смещение списка, но не его размер, так что пока мы используем только 3 объекта, игнорируя остальные. Вы научитесь кэшировать ответ чуть позже.</p><p>Каждый раз, когда пользователь нажимает кнопку обновления, поток запросов должен сгенерировать URL, чтобы мы могли получить новые данные. Для этой задачи нам потребуется сделать две вещи: реализовать поток событий нажатия на кнопку, а также изменить поток запросов так, чтобы он реагировал на события потока нажатий. К счастью, RxJS позволяет нам преобразовать обычные JavaScript-события в Observable.</p><p>Давайте поменяем поток запросов так, чтобы при нажатии кнопки обновления генерировался URL со случайным параметром смещения списка.</p><p>Похоже, мы что-то сломали. Теперь запрос срабатывает только после того, как мы нажали кнопку. Но по условию задачи мы должны делать запрос и при инициализации. Давайте попробуем починить наш код.</p><p>Для начала создадим разные потоки для вышеупомянутых условий:</p><p>Но как же теперь соединить события этих двух потоков в один? В этом нам поможет функция merge(). Вот визуальное представление того, что она делает:</p><p>Ну, теперь все очень просто:</p><p>Также есть альтернативный и более чистый способ реализовать задачу без вспомогательных переменных:</p><p>А можно и еще короче!</p><p>Функция startWith() делает как раз то, что нам нужно. Не важно, как вы реализовали поток: если вы вызвали функцию startWith(x), x будет начальным значением.</p><p>Вы заметили, что у нас дублируется URL? Давайте избавимся от дубликата, передвинув startWith() поближе к refreshClickStream, чтобы эмулировать нажатие кнопки при инициализации приложения:</p><p>То, что надо!</p><h4>Моделируем 3 рекомендации с помощью потоков</h4><p>Теперь, вместе с кнопкой обновления, у нас появилась проблема: при нажатии этой кнопки текущие 3 рекомендации не исчезают. Новые предложения появляются, как только с сервера пришел ответ, но для того, чтобы наш UI выглядел отзывчивым, мы должны очищать текущие предложения сразу же.</p><p>Теперь у нас есть два подписчика, влияющих на DOM-элементы (другой подписывается на responseStream) и это соответствует принципу “<a href="https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8">Разделяй и властвуй</a>“. Вы еще помните мантру реактивного подхода? Напоминаем:</p><figure><img src="https://media.tproger.ru/uploads/2017/02/687474703a2f2f692e696d6775722e636f6d2f4149696d5138432e6a7067.jpg" alt="" /></figure><p>Давайте сделаем так, чтобы рекомендации были потоками, в которых каждое генерируемое значение — это JSON-объект, содержащий данные рекомендации. Мы сделаем по потоку на каждую из трех рекомендаций. Вот так выглядит поток для рекомендации №1:</p><p>Скопипастим этот код для потока №2 (suggestion2Stream) и №3 (suggestion3Stream). Подумайте над тем, как можно избежать дублирования кода в данном примере. Это будет отличным упражнением. А еще и очень хорошим способом избежать <a href="https://tproger.ru/translations/last-line-effect/">эффекта последней строки</a>.</p><p>Вместо того, чтобы рендерить данные в методе subscribe() потока responseStream, мы сделаем это здесь:</p><p>Теперь мы можем обнулять рекомендацию при нажатии кнопки обновления:</p><p>Этот код будет интерпретировать null, как “нет данных” и скрывать DOM-элемент.</p><p>Вот диаграмма того, что мы только что реализовали:</p><p>В данном случае N — это null.</p><p>В качестве бонуса мы также можем выводить “пустые” рекомендации при инициализации с помощью startWith(null):</p><h3>Удаление рекомендации и кэширование ответа сервера</h3><p>Последняя функция, которую мы реализуем — удаление рекомендации. Напротив каждой рекомендации есть кнопка удаления, которая закрывает текущую рекомендацию и показывает вместо неё новую. Первая мысль, которая может у вас возникнуть — нужно делать новый запрос по нажатию этой кнопки:</p><p>Это не сработает. Перезагрузятся все рекомендации. Существует несколько способов решения этой проблемы, и один из наиболее оптимальных — повторное использование предыдущего ответа сервера. Ответ API состоит из списка длиной в 100 элементов, из которых мы используем только 3. Нет надобности запрашивать новые данные, когда мы можем использовать 97 “свежих”.</p><p>Когда происходит нажатие кнопки “close1”, нам нужно использовать наиболее свежий ответ сервера, чтобы получить случайного пользователя из списка. Примерно так:</p><p>В Rx* есть функция-комбинатор combineLatest, которая принимает два потока A и B в качестве входных данных и, когда один из потоков генерирует значение, возвращает два наиболее свежих значения из A и B. Посмотрим на диаграмму того, что она делает:</p><p>Мы можем применить функцию combineLatest() на потоках close1ClickStream и responseStream для того, чтобы после клика по кнопке “close1” мы получали последний ответ сервера и генерировали новое значение в suggestion1Stream. С другой стороны combineLatest() симметрична: когда новый ответ генерируется в responseStream, он будет скомбинирован с последним кликом “close 1” и выведет новую рекомендацию. Это позволяет нам упростить наш код suggestion1Stream, который мы писали ранее:</p><p>В этой схеме не хватает только одной детали: combineLatest() использует наиболее свежие данные из двух источников, но если один из источников еще не сгенерировал ни одного значения, combineLatest() не произведет события в поток вывода. Если вы посмотрите на диаграмму выше, вы можете заметить, что на выходе ничего нет, когда первый поток генерирует значение a. Только когда второй поток генерирует значение b, на выходе появляется значение.</p><p>Есть несколько способов решения этой проблемы и мы воспользуемся простейшим: симулированием клика кнопки “close1” при инициализации:</p><p>Все, курс молодого бойца по реактивному программированию окончен! Вы можете взглянуть на <a href="http://jsfiddle.net/staltz/8jFJH/48/">рабочий код</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Знакомство с RxSwift: примеры кода реактивного программирования на языке Swift</title>
      <link>https://tproger.ru/articles/rxswift</link>
      <comments>https://tproger.ru/articles/rxswift?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Компания Noveo]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/rxswift</guid>
      <description><![CDATA[<p>Реактивное программирование на Swift 3 глазами практикующего iOS-разработчика: разбор фреймворка RxSwift с примерами кода и пояснениями.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/rxswift">Знакомство с RxSwift: примеры кода реактивного программирования на языке Swift</a>»</p>]]></description>
      <category><![CDATA[Мобильная разработка]]></category>
      <category><![CDATA[iOS]]></category>
      <category><![CDATA[Swift]]></category>
      <category><![CDATA[Материалы от друзей Tproger]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Wed, 08 Feb 2017 19:03:03 GMT</pubDate>
      <content:encoded><![CDATA[<p>Около года назад наш iOS-разработчик компании Noveo, Александр, заинтересовался RxSwift. Столкнувшись с нехваткой документации, Саша решил самостоятельно упорядочить все приобретенные в ходе изучения фреймворка знания — для себя и для других. Результатом стала одна из его статей о Swift 2.2. Со времени ее публикации, конечно, и RxSwift, и сам Swift эволюционировали, и материал нуждался в обновлении, и другой iOS-разработчик из Noveo, Михаил, адаптировал материал для Swift 3. После редакции статья заиграла свежими красками, и на этом мы передаем слово нашим коллегам.</p><p>Заинтересовавшись темой функционального программирования, я встал на распутье: какой фреймворк выбрать для ознакомления? ReactiveCocoa — ветеран в iOS-кругах, информации по нему вдоволь. Но он вырос из Objective-C, и хотя это не является проблемой, все же в данный момент я в основном пишу на Swift, и хотелось бы взять решение, изначально спроектированное с учетом всех плюшек этого языка. RxSwift же — порт Reactive Extensions; последний, конечно, имеет долгую историю, но сам порт свежий и написан как раз под Swift. На нем я и решил остановиться.</p><p>Как выяснилось, документация по RxSwift несколько специфична: описание всех команд ведет на <a href="http://reactivex.io/">http://reactivex.io/</a>, а там в основном дается общая информация, у разработчиков еще не дошли руки сделать документацию именно для RxSwift, что не всегда удобно. Некоторые команды имеют тонкости в реализации, а есть такие, о которых в общей документации нет ничего, кроме упоминания.</p><p>Прочитав все главы вики с гитхаба RxSwift, я решил сразу поразбираться с официальными примерами; тут-то и стало ясно, что с RX такое не пройдет, нужно хорошо понимать основы, иначе будешь как мартышка с копипастом гранатой. Я начал разбирать самые сложные для понимания команды, а потом перешел к тем, что были вроде и понятны, но задавая себе вопросы по ним, я лишь догадывался, как верно ответить, и уверенности в моих ответах у меня не было.</p><p>В общем, я решил проработать все операторы RxSwift. Лучший способ что-то понять в программировании — запустить код и посмотреть, как он отработает. Учитывая специфику реактивного программирования, лучше дополнить это схемами (они бывают очень полезны), ну и кратким описанием на русском. Закончив сегодня работу, я подумал, что грех не поделиться результатами с тем, кто лишь присматривается к теме реактивного программирования.</p><p><b>Много картинок и текста ниже, очень много!</b></p><p>Предварительно я рекомендую просмотреть <a href="https://github.com/ReactiveX/RxSwift">официальную документацию</a>, у меня передана основная суть и специфика RxSwift команд, а не основы.<br />Так же можно “поиграться” с шариками в схемах, так называемые <a href="http://rxmarbles.com/">RxMarbles</a>, есть бесплатная версия под <a href="https://itunes.apple.com/ru/app/rxmarbles/id1087272442?mt=8">iPhone/iPad</a>.</p><p>Итак, в этой статье я рассмотрю все (ну или почти все) команды RxSwift, дам для каждой краткое описание, схему (если это имеет смысл), код, результат выполнения, а при необходимости сделаю комментарии по выводу в лог результатов выполнения кода.<br />В статье заголовок каждой команды — ссылка на на официальную документацию, т.к. я не ставил перед собой цели перевести все нюансы по командам.<br />Вот и <a href="https://github.com/sparklone/RxSwift/blob/master/RXSwift%20operators.pdf">ссылка конкретно на PDF</a>, где в виде mindMap собраны все команды, что позволяет быстро просмотреть их все. Кусочки кода в PDF приложены для того, чтобы увидеть, как и с каким параметрами нужно работать с командой. Изначально ради этого PDF я все и затеял — чтобы иметь под рукой документ, в котором наглядно видны все команды с их схемами. PDF получился огромным (в плане рабочего пространства, а не веса), но я проверял, даже на iPad 2 все нормально просматривается.</p><p>Обо всех ошибках просьба писать в личку, объем работ оказался слегка великоват, после четвертой вычитки текста мои глаза меня прокляли.</p><p>Что ж, надеюсь, моя работа кому-то пригодится. Приступим.</p><h2>Содержание</h2><p><a href="https://tproger.ru/#Intro">Заметки</a></p><h2>Создание Observable</h2><p><a href="https://tproger.ru/#asObservable">asObservable</a><br /><a href="https://tproger.ru/#create">create</a><br /><a href="https://tproger.ru/#deferred">deferred</a><br /><a href="https://tproger.ru/#empty">empty</a><br /><a href="https://tproger.ru/#error">error</a><br /><a href="https://tproger.ru/#interval">interval</a><br /><a href="https://tproger.ru/#just">just</a><br /><a href="https://tproger.ru/#never">never</a><br /><a href="https://tproger.ru/#of">of</a><br /><a href="https://tproger.ru/#range">range</a><br /><a href="https://tproger.ru/#repeatElement">repeatElement</a><br /><a href="https://tproger.ru/#timer">timer</a></p><h2>Комбинирование Observable</h2><p><a href="https://tproger.ru/#amb">amb</a><br /><a href="https://tproger.ru/#combineLatest">combineLatest</a><br /><a href="https://tproger.ru/#concat">concat</a><br /><a href="https://tproger.ru/#merge">merge</a><br /><a href="https://tproger.ru/#startWith">startWith</a><br /><a href="https://tproger.ru/#switchLatest">switchLatest</a><br /><a href="https://tproger.ru/#withLatestFrom">withLatestFrom</a><br /><a href="https://tproger.ru/#zip">zip</a></p><h2>Фильтрация</h2><p><a href="https://tproger.ru/#distinctUntilChanged">distinctUntilChanged</a><br /><a href="https://tproger.ru/#elementAt">elementAt</a><br /><a href="https://tproger.ru/#filter">filter</a><br /><a href="https://tproger.ru/#ignoreElements">ignoreElements</a><br /><a href="https://tproger.ru/#sample">sample</a><br /><a href="https://tproger.ru/#single">single</a><br /><a href="https://tproger.ru/#skip">skip</a><br /><a href="https://tproger.ru/#skipDuration">skip (duration)</a><br /><a href="https://tproger.ru/#skipUntil">skipUntil</a><br /><a href="https://tproger.ru/#skipWhile">skipWhile</a><br /><a href="https://tproger.ru/#skipWhileWithIndex">skipWhileWithIndex</a><br /><a href="https://tproger.ru/#take">take</a><br /><a href="https://tproger.ru/#takeDuration">take (duration)</a><br /><a href="https://tproger.ru/#takeLast">takeLast</a><br /><a href="https://tproger.ru/#takeUntil">takeUntil</a><br /><a href="https://tproger.ru/#takeWhile">takeWhile</a><br /><a href="https://tproger.ru/#takeWhileWithIndex">takeWhileWithIndex</a><br /><a href="https://tproger.ru/#debounce">debounce</a></p><h2>Трансформация</h2><p><a href="https://tproger.ru/#buffer">buffer</a><br /><a href="https://tproger.ru/#flatMap">flatMap</a><br /><a href="https://tproger.ru/#flatMapFirst">flatMapFirst</a><br /><a href="https://tproger.ru/#flatMapLatest">flatMapLatest</a><br /><a href="https://tproger.ru/#flatMapWithIndex">flatMapWithIndex</a><br /><a href="https://tproger.ru/#map">map</a><br /><a href="https://tproger.ru/#mapWithIndex">mapWithIndex</a><br /><a href="https://tproger.ru/#window">window</a></p><h2>Операторы математические и агрегирования</h2><p><a href="https://tproger.ru/#reduce">reduce</a><br /><a href="https://tproger.ru/#scan">scan</a><br /><a href="https://tproger.ru/#toArray">toArray</a></p><h2>Работа с ошибками</h2><p><a href="https://tproger.ru/#catchError">catchError</a><br /><a href="https://tproger.ru/#catchErrorJustReturn">catchErrorJustReturn</a><br /><a href="https://tproger.ru/#retry">retry</a><br /><a href="https://tproger.ru/#retryWhen">retryWhen</a></p><h2>Операторы для работы с Connectable Observable</h2><p><a href="https://tproger.ru/#multicast">multicast</a><br /><a href="https://tproger.ru/#publish">publish</a><br /><a href="https://tproger.ru/#refCount">refCount</a><br /><a href="https://tproger.ru/#replay">replay</a><br /><a href="https://tproger.ru/#replayAll">replayAll</a></p><h2>Вспомогательные методы</h2><p><a href="https://tproger.ru/#debug">debug</a><br /><a href="https://tproger.ru/#do">do</a><br /><a href="https://tproger.ru/#delaySubscription">delaySubscription</a><br /><a href="https://tproger.ru/#observeOn">observeOn</a><br /><a href="https://tproger.ru/#subscribe">subscribe</a><br /><a href="https://tproger.ru/#subscribeOn">subscribeOn</a><br /><a href="https://tproger.ru/#timeout">timeout</a><br /><a href="https://tproger.ru/#using">using</a></p><p><br />В схемах я буду использовать обозначение <b>Source/SO</b> в качестве <b>Source Observable</b>, <b>RO/Result</b> в качестве <b>Result Observable</b>.</p><p>Функция example просто позволяет отделять вывод в консоли, её код следующий (взят из RxSwift):</p><p>Во всех примерах, где необходимо работать с временными задержками, если этот код будет запускаться в песочнице, необходимо прописать</p><p>Также подразумевается, что читатель имеет общее представление о реактивном программировании в общем и об RxSwift в частности. Не знаю, есть ли смысл городить очередную вводную.</p><h2>Создание Observable</h2><h3>asObservable</h3><p>Этот метод реализован в классах RxSwift, если они поддерживают конвертацию в Observable. Например: ControlEvent, ControlProperty, Variable, Driver</p><p>Консоль:</p><p>В данном примере мы Variable преобразовали в Observable и подписались на его события.</p><h3>create</h3><p>Этот метод позволяет создавать Observable с нуля, полностью контролируя, какие элементы и когда он будет генерировать.</p><p>Консоль:</p><p>В данном примере мы создали Observable, который сгенерирует несколько значений, и в конце вызовется complete.</p><h3>deferred</h3><p>Этот оператор позволяет отложить создание Observable до момента подписки с помощью subscribe.</p><p>Консоль:</p><p>В первом случае Observable создается сразу, с помощью Observable.just(i), и изменение значения i уже не влияет на генерируемый этой последовательностью элемент. Во втором же случае мы создаем Observable с помощью deferred и можем поменять значение i перед subscribe.</p><h3>empty</h3><p>Пустая последовательность, заканчивающаяся Completed.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/bab24735e0de416792a959cdcd519180.png" alt="" /></figure><p>Консоль:</p><h3>error</h3><p>Создаст последовательность, которая состоит из одного события – Error.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b3439cb513de4be9996f48bebfb6aaf3.png" alt="" /></figure><p>Консоль:</p><h3>interval</h3><p>Создает бесконечную последовательность, возрастающую с 0 с шагом 1 с указанной периодичностью.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/011ba04fda0a478c9e0a225fb36748ec.png" alt="" /></figure><p>Консоль:</p><h3>just</h3><p>Создает последовательность из любого значения, которая завершается Completed.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/0e8fac44dcb44bbc86a8a0ed452aec7b.png" alt="" /></figure><p>Консоль:</p><h3>never</h3><p>Пустая последовательность, чьи observer’ы никогда не вызываются, т.е. не будет сгенерировано ни одно событие.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/503a867579524903afe5594b3c1fc6dd.png" alt="" /></figure><p>Консоль:</p><h3>of</h3><p>Последовательность из переменного количества элементов. После всех элементов генерируется Completed.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/96235583734c4667a247f7279f62609c.png" alt="" /></figure><p>Консоль:</p><p>В первом случае мы создали последовательность из двух чисел, во втором — из двух Observable, а затем объединили их между собой с помощью оператора merge.</p><h3>range</h3><p>Создает последовательность с конечным числом элементов, возрастающую с шагом 1 от указанного значения указанное число раз, после всех элементов генерируется Completed.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b8c645c248954839b6ba4e6c86a56979.png" alt="" /></figure><p>Консоль:</p><p>Сгенерировались элементы, начиная с 5, 3 раза с шагом 1.</p><h3>repeatElement</h3><p>Бесконечно создавать указанный элемент, без задержек. Никогда не будут сгенерированы события Completed или Error.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/0e8bde18055743ad87475af402ab60f9.png" alt="" /></figure><p>Консоль:</p><h3>timer</h3><p>Бесконечная последовательность, возрастающая с 0 с шагом 1, с указанной периодичностью и возможностью задержки при старте. Никогда не будут сгенерированы события Completed или Error.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/f8e230c64e5a4f2999f9fc10eb7b0815.png" alt="" /></figure><p>Консоль:</p><p>В данном примере последовательность начнет генерировать элементы с задержкой в 2 секунды, каждые 3 секунды.</p><h2>Комбинирование Observable</h2><h3>amb</h3><p>Из всех Observable SO выбирается тот, который первым начинает генерировать элементы, его элементы и дублируются в RO, остальные SO игнорируются.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/6e68723996bd488a830ffb4b6cf35ea2.png" alt="" /></figure><p>Консоль:</p><p>Т.к. первым сгенерировал элемент subjectC, лишь его элементы дублируются в RO, остальные игнорируются.</p><h3>combineLatest</h3><p>Как только все Observable сгенерировали хотя бы по одному элементу, эти элементы используются в качестве параметров в переданную функцию, и результат этой функции генерируется RO в качестве элемента. В дальнейшем при генерации элемента любым Observable генерируется новый результат функции с последними элементами из всех комбинируемых Observable.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/d1e398ec43d44c08ab5670293a15d853.png" alt="" /></figure><p>Консоль:</p><p>В этом примере я создал Observable с помощью timer — для генерации элементов с разной задержкой, чтобы было видно, как перемешиваются элементы. К моменту появления первого элемента sequenceA появилось уже три элемента sequenceB. Поэтому первым элементом в RO последовательности стала пара объектов A0 – B2.</p><h3>concat</h3><p>В RO элементы включают сначала все элементы первого Observable, и лишь затем следующего. Это означает, что если первый Observable никогда не сгенерирует Completed, элементы второго никогда не поступят в RO. Ошибка в текущем Observable пробрасывается в RO.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/125e09b9c0df42f39a1792e0192e8b9a.png" alt="" /></figure><p>Консоль:</p><h3>merge</h3><p>Элементы RO включают элементы из исходных Observable в том порядке, в котором они были выпущены в исходных Observable.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/8c24ab2ed59a41a6afd992e149d2c4fb.png" alt="" /></figure><p>Консоль:</p><p>Последовательности сделаны с задержкой в генерации, и видно, что элементы в RO теперь вперемешку, в том порядке, в котором они были сгенерированы в исходных Observable.</p><h3>startWith</h3><p>В начало SO добавляются элементы, переданные в качестве аргумента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/0c23f3479a1b453788863898b10126ca.png" alt="" /></figure><p>Консоль:</p><h3>switchLatest</h3><p>Изначально подписываемся на O1 генерируемого SO, его элементы зеркально генерируются в RO. Как только из SO генерируется очередной Observable, элементы предыдущего Observable отбрасываются, т.к. происходит отписка от O1, подписываемся на O2 и так далее. Таким образом в RO — элементы лишь из последнего сгенерированного Observable.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/822951742ec64c7396cf8fb92151cbba.png" alt="" /></figure><p>Консоль:</p><p>В первом примере показано, как команда работает в статике, когда мы руками переподключаем Observable.<br />Во втором примере оператором create создан Observable&lt;Observable&gt;, AnyObserver которого мы вынесли в переменную, по таймеру получающую новый Observable, который реализован в виде таймера. Т.к. задержки у таймеров разные, то можно наблюдать. как с помощью switchLatest в RO попадают значения из последнего сгенерированного таймера.</p><h3>withLatestFrom</h3><p>Как только O1 генерирует элемент, проверяется, сгенерирован ли хоть один элемент в O2, и если да, то берутся последние элементы из O1 и O2 и используются в качестве аргументов для переданной функции, результат которой генерируется RO в качестве элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/cb8c5be5a30442e7b16e3eac78651a02.png" alt="" /></figure><p>Консоль:</p><h3>zip</h3><p>Элементы RO представляют собой комбинацию из элементов, сгенерированных исходными Observable, объединение идет по индексу выпущенного элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b9fa76866cf64559bb7d1bb024adb35c.png" alt="" /></figure><p>Консоль:</p><p>Из примеров видно, что элементы комбинируются попарно в том порядке, в каком они были сгенерированы в исходных Observable.</p><h2>Фильтрация</h2><h3>distinctUntilChanged</h3><p>Пропускаем все повторяющиеся <b>подряд идущие</b> элементы.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/3838bfaba1b345069682fd5561e8f24e.png" alt="" /></figure><p>Консоль:</p><p>Здесь тонкий момент: отбрасываются не уникальные для всей последовательности элементы, а лишь те, которые идут подряд.</p><h3>elementAt</h3><p>В RO попадает лишь элемент, выпущенный N по счету.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/2058c5dfd4c44783846cb3142afb3e3e.png" alt="" /></figure><p>Консоль:</p><h3>filter</h3><p>Отбрасываются все элементы, которые не удовлетворяют заданным условиям.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/ff1a054945cb42ab9093f7b68b3645e8.png" alt="" /></figure><p>Консоль:</p><h3>ignoreElements</h3><p>Отбрасывает все элементы, передаёт только терминальные сообщения Completed и Error.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/0870adbdffa44870a1213f08d7b29b8b.png" alt="" /></figure><p>Консоль:</p><h3>sample</h3><p>При каждом сгенерированном элементе последовательности семплера (воспринимать как таймер) — брать <b>последний</b> выпущенный элемент исходной последовательности и дублировать его в RO, ЕСЛИ он не был сгенерирован ранее.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/f692683e77ca44089e106d7ac704393d.png" alt="" /></figure><p>Консоль:</p><h3>single</h3><p>Из исходной последовательности берется единственный элемент, если элементов &gt; 1 — генерировать ошибку. Есть вариант с предикатом.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/2126d02199a8426d9617a7e5a3c8ad96.png" alt="" /></figure><p>Консоль:</p><p>В первом примере в исходной последовательности оказалось больше 1 элемента, поэтому была сгенерирована ошибка в момент генерирования в SO второго элемента.<br />Во втором примере условиям предиката удовлетворил всего 1 элемент, поэтому ошибки сгенерировано не было.</p><h3>skip</h3><p>Из SO отбрасываем первые N элементов.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/19470a3a89e941949aa7a5c9a8a87e7a.png" alt="" /></figure><p>Консоль:</p><h3>skip (duration)</h3><p>Из SO отбрасываем первые элементы, которые были сгенерированы в первые N.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/82e042f1d6b04781beb1f172c5489f36.png" alt="" /></figure><p>Консоль:</p><h3>skipUntil</h3><p>Отбрасываем из SO элементы, которые были сгенерированы до начала генерации элементов последовательностью, переданной в качестве параметра.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/e9fa880557014f69865c887368ae896f.png" alt="" /></figure><p>Консоль:</p><p>Генерация элементов в secondSequence была отложена на 1 секунду с помощью команды delaySubscription, таким образом элементы из firstSequence стали дублироваться в RO лишь через 1 секунду.</p><h3>skipWhile</h3><p>Отбрасываем из SO элементы до тех пор, пока функция, переданная в качестве параметра, возвращает true.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/373863a24f6b4cb6b9cbf82f858f241f.png" alt="" /></figure><p>Консоль:</p><h3>skipWhileWithIndex</h3><p>Отбрасываем из SO элементы до тех пор, пока пока функция, переданная в качестве параметра, возвращает true. Отличие от skipWhile в том, что еще одним параметром, переданным в функцию, является индекс сгенерированного элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/a7d9690cd41d4ab59a77e620e80740d7.png" alt="" /></figure><p>Консоль:</p><h3>take</h3><p>Из SO берутся лишь первые N элементов.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/65c0b9b983894312a9f5b147d5bef422.png" alt="" /></figure><p>Консоль:</p><h3>take (duration)</h3><p>Из SO берутся лишь элементы, сгенерированные в первые N секунд.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b799656eaf11412187d8dcf5697e616a.png" alt="" /></figure><p>Консоль:</p><h3>takeLast</h3><p>Из SO берутся лишь последние N элементов. Что означает: если SO никогда не закончит генерировать элементы, в RO не попадет ни одного элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/4b197f2530e043b0bd5ee13dd43cc445.png" alt="" /></figure><p>Консоль:</p><p>Второй пример приведен для иллюстрации в задержке генерации элементов в RO из-за ожидания завершения генерации элементов в SO.</p><h3>takeUntil</h3><p>Из SO берутся элементы, которые были выпущены до начала генерации элементов последовательностью, переданной в качестве параметра.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/f62c4c834e63461da88d8b208c037e14.png" alt="" /></figure><p>Консоль:</p><h3>takeWhile</h3><p>Из SO берутся элементы до тех пор, пока функция, переданная в качестве параметра, возвращает true.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/2b6e59c4c0fb42c3a7f11da4cdf85a36.png" alt="" /></figure><p>Консоль:</p><h3>takeWhileWithIndex</h3><p>Из SO берутся элементы до тех пор, пока функция, переданная в качестве параметра, возвращает true. Отличие от takeWhile в том, что еще одним параметром, переданным в функцию, является индекс сгенерированного элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/445d0b259f054d8bbe21218fba5a4f18.png" alt="" /></figure><p>Консоль:</p><h3>debounce</h3><p>Из SO берутся лишь элементы, после которых не было новых элементов N секунд.</p><p>Консоль:</p><p>В данном примере элементы генерируются c разными задержками. Поэтому debounce сработает всего несколько раз в момент, когда между элементами будет достаточный временной промежуток.</p><h2>Трансформация</h2><h3>buffer</h3><p>Элементы из SO по определенным правилам объединяются в массивы и генерируются в RO. В качестве параметров передаются count (максимальное число элементов в массиве) и timeSpan (время максимального ожидания наполнения текущего массива из элементов SO). Таким образом, элемент RO являет собой массив [T] длиной от 0 до count.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/caa2896ac5104866a1c405c3e803aa84.png" alt="" /></figure><p>Консоль:</p><p>Максимальное число элементов в массиве указано равное трем, и оно никак не влияет на наполнение массива в данном примере. За максимальное время ожидания наполнения таймер успевает сгенерировать не более 2 элементов. Однако период таймера и время ожидания наполнения выбраны таким образом, что третий массив успеет получить лишь один объект.</p><h3>flatMap</h3><p>Каждый элемент SO превращается в отдельный Observable, и все элементы из [O1, O2, O3…] объединяются в RO. Порядок генерации элементов в RO зависит от времени их генерации в исходных [O1, O2, O3…] (как в команде merge).</p><figure><img src="https://media.tproger.ru/uploads/2017/02/6d75e2dce2bf42978ce604aae5c2217f.png" alt="" /></figure><p>Консоль:</p><h3>flatMapFirst</h3><p>Каждый элемент SO превращается в отдельный Observable.<br />1) Изначально подписываемся на O1, его элементы зеркально генерируются в RO. Пока O1 генерирует элементы, все последующие Observable, сгенерированные из SO, отбрасываются, на них не подписываемся.<br />2) как только O1 оканчивается, если будет сгенерирован новый Observable, на него подпишутся, и его элементы будут дублироваться в RO.<br />Повторяем пункт 1, но вместо O1 берем последний сгенерированный Observable.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/d32de4c093da4668a1a6bb78ba4ef1c1.png" alt="" /></figure><p>Консоль:</p><p>В примере благодаря задержкам в генерации мы видим, что, пока не произойдет окончание первого Observable, никакой новый Observable его не заменит.</p><h3>flatMapLatest</h3><p>Каждый элемент SO превращается в отдельный Observable. Изначально подписываемся на O1, его элементы зеркально генерируются в RO. Как только из SO выпускается очередной элемент и на его основе генерируется очередной Observable, элементы предыдущего Observable отбрасываются, т.к. происходит отписка. Таким образом в RO — элементы из последнего генерированного Observable.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/6648c2e110b14a6faa31ba7b9ecb72a7.png" alt="" /></figure><p>Консоль:</p><p>В примере благодаря задержкам в генерации мы видим, что, как только генерируется новый Observable, происходит отписка от предыдущего Observable.</p><h3>flatMapWithIndex</h3><p>Каждый элемент SO превращается в отдельный Observable, и все элементы из [O1, O2, O3…] объединяются в RO. Порядок генерации элементов в RO зависит от времени их генерации в исходных [O1, O2, O3…] (как в команде merge). Отличие от flatMap в том, что еще одним параметром, переданным в функцию, является индекс сгенерированного элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/6d75e2dce2bf42978ce604aae5c2217f-1.png" alt="" /></figure><p>Консоль:</p><h3>map</h3><p>Элементы SO преобразуются, не меняя порядок их генерации. Можно менять не только значение, но и тип элементов.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/8bad564047d141d9a034791fe1c1218b.png" alt="" /></figure><p>Консоль:</p><h3>mapWithIndex</h3><p>Элементы SO преобразуются, не меняя порядок их генерации. Можно менять не только значение, но и тип элементов. Отличие от map в том, что еще одним параметром, переданным в функцию, является индекс сгенерированного элемента.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/8bad564047d141d9a034791fe1c1218b-1.png" alt="" /></figure><p>Консоль:</p><h3>window</h3><p>Элементы из SO по определенным правилам передаются в генерирующиеся новые Observable. В качестве параметров передаются count (максимальное число элементов, которые будут сгенерированы каждым Observable) и timeSpan (время максимального ожидания наполнения текущего Observable из элементов SO). Таким образом элемент RO являет собой Observable, число генерируемых элементов которого равно от 0 до N. Основное отличие от bufffer в том, что элементы SO зеркалятся сгенерированными Observable моментально, а в случае buffer мы вынуждены ждать указанное в качестве параметра максимальное время (если буфер не заполнится раньше).</p><figure><img src="https://media.tproger.ru/uploads/2017/02/110eb190627c461a825632af0b280c7d.png" alt="" /></figure><p>Консоль:</p><p>В примере используются временные задержки, что помогает добиться частичной наполненности генерируемых Observable.</p><h2>Операторы математические и агрегирования</h2><h3>reduce</h3><p>Каждый элемент SO преобразуется с помощью переданной функции, результат операции передается в качестве параметра в функцию на следующем шаге. Как только SO генерирует терминальное состояние, RO генерирует результат, т.е. RO сгенерирует лишь один элемент.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/e01c119b5b6b4211a931a67590f52531.png" alt="" /></figure><p>Консоль:</p><h3>scan</h3><p>Каждый элемент SO преобразуется с помощью переданной функции, результат операции генерируется в RO, но, кроме этого, оно передается в качестве параметра в функцию на следующем шаге. В отличии от reduce число элементов в RO равно числу элементов в SO.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/17db4b16115a453bafe022cfcec185e0.png" alt="" /></figure><p>Консоль:</p><h3>toArray</h3><p>Все элементы из SO после генерации терминального состояния объединяются в массив, и генерируются RO.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/de6470e773f34bb5bc5de7a3dece4568.png" alt="" /></figure><p>Консоль:</p><h2>Работа с ошибками</h2><h3>catchError</h3><p>Позволяет перехватить сгенерированную ошибку из SO и заменить ее на новый Observable, который теперь будет генерировать элементы.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/543094bc6d3c405e8a59a61cf3aeb805.png" alt="" /></figure><p>Консоль:</p><p>После генерации очередного элемента была сгенерирована ошибка, но мы её перехватили и вернули взамен новый Observable.</p><h3>catchErrorJustReturn</h3><p>Позволяет перехватить сгенерированную ошибку из SO и заменить её на указанный элемент, после этого SO генерирует Completed.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/1881688f64e342bbb60057e7c9cc1259.png" alt="" /></figure><p>Консоль:</p><p>После генерации очередного элемента была сгенерирована ошибка, но мы её перехватили и вернули взамен новый элемент.</p><h3>retry</h3><p>Позволяет перехватить сгенерированную ошибку из SO и в зависимости от переданного параметра попытаться запустить SO c начала нужное число раз в надежде, что ошибка не повторится.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/6d8f89d8264c4e6194344233b9cb4474.png" alt="" /></figure><p>Консоль:</p><p>Передаваемое в оператор целое число означает количество попыток дождаться успешного окончания. 0 — ни одной попытки, т.е. цепочка ни разу не будет запущена, и просто произойдет completed событие. 1 — такое же поведение, как будто оператор и не не был применен. 2 — исходная попытка + дополнительная, и т.д. Если в оператор не передать параметр, то количество попыток повторить будет бесконечным.</p><h3>retryWhen</h3><p>Позволяет перехватить сгенерированную ошибку из SO, и в зависимости от типа ошибки мы либо повторно генерируем ошибку, которая пробрасывается в RO, и на этом выполнение заканчивается, либо генерируем Observable (tryObservable), генерация каждого корректного элемента которого выполнит повторную подписку на SO в надежде, что ошибка исчезнет. Если tryObservable заканчивается ошибкой, она пробрасывается в RO, и на этом выполнение заканчивается.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b1b23ed4c6e44e3ca27941bc00a838ed.png" alt="" /></figure><p>Консоль:</p><p>Я встроил инкремент переменной i в генерацию sequenceWithError, чтобы на 3й попытке ошибка исчезла. Если раскоментировать генерацию ошибки RxError.Overflow, мы её не перехватим в операторе retryWhen и пробросим в RO.</p><h2>Операторы для работы с Connectable Observable</h2><h3>multicast</h3><p>Позволяет проксировать элементы из исходной SO на Subject, переданный в качестве параметра. Подписываться нужно именно на этот Subject, генерация элементов Subject начнется после вызова оператора connect.</p><p>Консоль:</p><h3>publish</h3><p>publish = multicast + replay subject<br />Позволяет создавать Connectable Observable, которые не генерируют события даже после subscribe. Для старта генерации таким Observable нужно дать команду connect. Это позволяет подписать несколько Observer к одному Observable и начать генерировать элементы одновременно, вне зависимости от того, когда был выполнен subscribe.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/320763dc7b664b16bb409c0df63c25e9.png" alt="" /></figure><p>Консоль:</p><p>Как видно, хоть подписка и была произведена в разное время, пока не вызвали команду connect — генерация элементов не началась. Зато благодаря команде debug видно, что даже после того как все отписались, последовательность продолжила генерировать элементы.</p><h3>refCount</h3><p>Позволяет создать обычный Observable из Connectable. После первого вызова subscribe к этому обычному Observable происходит подписка Connectable на SO.<br />Получается что-то вроде</p><p>SO будет продолжать генерировать элементы до тех пор, пока есть хотя бы один подписанный на refCountSequence. Как только все подписки на refCountSequence аннулируются, происходит отписка и publishSequence от SO.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b3fcbd12e07243918bc05474121cbf2b.png" alt="" /></figure><p>Консоль:</p><h3>replay</h3><p>Если SO обычный, — конвертирует его в Connectable. После этого все, кто подпишутся на него после вызова connect(), мгновенно получат в качестве первых элементов последние сгенерированные N элементов. Даже если отпишутся все — Connectable будет продолжать генерировать элементы.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/b02bc70299014f27980c6af4e13628e4.png" alt="" /></figure><p>Консоль:</p><h3>replayAll</h3><p>Если SO обычный, — конвертирует его в Connectable. Все, кто подпишутся на него, после вызова connect() получат сначала все элементы, которые были сгенерированы ранее. Даже если отпишутся все, Connectable будет продолжать генерировать элементы.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/156f90599fd0442daeec695123fe7e3b.png" alt="" /></figure><p>Консоль:</p><h2>Вспомогательные методы</h2><h3>debug</h3><p>RO полностью дублирует SO, но логируются все события с временной меткой.</p><p>Консоль:</p><h3>do</h3><p>RO полностью дублирует SO, но мы встраиваем перехватчик всех событий из жизненного цикла SO.</p><p>Консоль:</p><h3>delaySubscription</h3><p>Дублирует элементы из SO в RO, но с временной задержкой, указанной в качестве параметра.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/26effc9b1a124e90873c3f030fac4786.png" alt="" /></figure><p>Консоль:</p><h3>observeOn</h3><p>Указывает, на каком Scheduler должен выполнять свою работу Observer, особенно критично при работе с GUI.</p><p>Консоль:</p><p>Как видно, благодаря observeOn мы смогли выполнить код внутри subscribe на другом потоке, хотя оба Observable были запущены на background.</p><h3>subscribe</h3><p>Оператор, связывающий Observable с Observer, позволяет подписаться на все события из Observable.</p><p>Консоль:</p><h3>subscribeOn</h3><p>Указывает, на каком Scheduler выполнять подписку на Observable. Редко используемый оператор. Для получения колбэков на нужном Scheduler следует пользоваться observeOn.</p><p>Консоль:</p><h3>timeout</h3><p>Дублирует элементы из SO в RO, но если в течение указанного времени SO не сгенерировало ни одного элемента, RO генерирует ошибку.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/d02a14d2152d43ef935af540e056dd4b.png" alt="" /></figure><p>Консоль:</p><p>using</p><h3>using</h3><p>Позволяет проинструктировать Observable создать ресурс, который будет жить лишь пока жив RO, в качестве параметров передаются 2 фабрики, одна генерирует ресурс, вторая — Observable из ресурса, у которых будет единое время жизни.</p><p>Консоль:</p><p>Как видно, после того, как Observable закончил генерировать элементы, у нашего ресурса Factory был вызван метод dispose.</p><figure><img src="https://media.tproger.ru/uploads/2017/02/hands-on-keyboard_white-logo-1-1024x396.png" alt="" /></figure><p>За материал выражаем благодарность международной IT-компании Noveo.</p>]]></content:encoded>
    </item>
    <item>
      <title>Принципы реактивного программирования с использованием библиотеки ReactiveX для Python на примере простого RSS-агрегатора</title>
      <link>https://tproger.ru/articles/reactivex-python</link>
      <comments>https://tproger.ru/articles/reactivex-python?scrollTo=tproger-comments</comments>
      <dc:creator><![CDATA[Компания Noveo]]></dc:creator>
      <guid isPermaLink="true">https://tproger.ru/articles/reactivex-python</guid>
      <description><![CDATA[<p>ReactiveX помогает создавать асинхронные и событийно-ориентированные программы с наблюдаемыми последовательностями и потоками данных.</p><p>— Читать дальше «<a rel="follow" href="https://tproger.ru/articles/reactivex-python">Принципы реактивного программирования с использованием библиотеки ReactiveX для Python на примере простого RSS-агрегатора</a>»</p>]]></description>
      <category><![CDATA[Python]]></category>
      <category><![CDATA[Материалы от друзей Tproger]]></category>
      <category><![CDATA[Реактивное программирование]]></category>
      <category><![CDATA[Статьи]]></category>
      <slash:comments>0</slash:comments>
      <pubDate>Tue, 04 Oct 2016 18:02:16 GMT</pubDate>
      <content:encoded><![CDATA[<p>В последние годы реактивное программирование в целом, а технология <a href="http://reactivex.io/">ReactiveX</a> в частности, обретает всё большую популярность среди разработчиков. Одни уже активно используют все преимущества этого подхода, а другие только “что-то слышали”. Со своей стороны я постараюсь помочь вам представить, насколько некоторые концепции реактивного программирования способны изменить взгляд на привычные, казалось бы, вещи.</p><p>Существует два принципиально различных способа организации больших систем: в соответствии с объектами и состояниями, которые живут в системе, и в соответствии с потоками данных, которые проходят через неё. Парадигма реактивного программирования предполагает легкость выражения потоков данных, а также  распространение изменений благодаря этим потокам. Например, в императивном программировании операция присваивания означает конечность результата, тогда как в реактивном значение будет пересчитано при получении новых входных данных. Поток значений проходит в системе ряд трансформаций, которые необходимы для решения определенной задачи. Оперирование потоками позволяет системе быть расширяемой и асинхронной, а правильная реакция на возникающие ошибки – отказоустойчивой.</p><p>ReactiveX – библиотека, позволяющая создавать асинхронные и событийно-ориентированные программы, использующие наблюдаемые последовательности. Она расширяет шаблон Наблюдателя для поддержки последовательностей данных, добавляет операторы для их декларативного соединения, избавляя от необходимости заботиться о синхронизации и безопасности потоков, разделяемых структурах данных и неблокирующего I/O.</p><p>Одним из основных отличий библиотеки ReactiveX от функционального реактивного программирования является то, что она оперирует не непрерывно изменяющимися, а дискретными значениями, которые “испускаются” в течении длительного времени.</p><p>Стоит немного рассказать о том, что такое Observer, Observable, Subject. Модель Observable является источником данных и позволяет обрабатывать потоки асинхронных событий похожим образом с тем, который вы используете для коллекций данных, таких как массивы. И всё это вместо колбэков, а значит, код является более читабельным и менее склонным к ошибкам.</p><p>В ReactiveX наблюдатель (Observer) подписывается на Observable и впоследствии реагирует на элемент или последовательность элементов, которые тот отправляет. У каждого Observer, подписанного на Observable, вызывается метод Observer.on_next() на каждый элемент потока данных, после которого может быть вызван как Observer.on_complete(), так и Observer.on_error(). Часто Observable применяется таким образом, что он не начинает отдавать данные до тех пор, пока кто-нибудь не подписывается на него. Это так называемые “ленивые вычисления” – значения вычисляются только тогда, когда в них возникает потребность.</p><figure><img src="https://media.tproger.ru/uploads/2016/10/image-1.jpg" alt="" /></figure><p>Бывают задачи, для решения которых нужно соединить Observer и Observable, чтобы принимать сообщения о событиях и сообщать о них своим подписчикам. Для этого существует Subject, имеющий, кроме стандартной, ещё несколько реализаций:</p><ul><li>ReplaySubject имеет возможность кэшировать все поступившие в него данные, а при появлении нового подписчика – отдавать всю эту последовательность сначала, работая далее в обычном режиме.</li><li>BehaviorSubject хранит последнее значение, по аналогии с ReplaySubject отдавая его появившемуся подписчику. При создании он получает значение по умолчанию, которое будет получать каждый новый подписчик, если последнего значения еще не было.</li><li>AsyncSubject также хранит последнее значение, но не отдает данные, пока не завершится вся последовательность.</li></ul><p>Observable и Observer – только начало ReactiveX. Они не несут в себе всю мощь, которую являют собой операторы,  позволяющие трансформировать, объединять, манипулировать последовательностями элементов, которые отдают Observable.</p><p>В документации ReactiveX <a href="http://reactivex.io/documentation/operators.html">описание операторов</a> включает в себя использование Marble Diagram. К примеру, вот как эти диаграммы представляют Observable и их трансформации:</p><figure><img src="https://media.tproger.ru/uploads/2016/10/legend.png" alt="" /></figure><p>Глядя на диаграмму ниже, легко понять, что оператор map трансформирует элементы, отдаваемые Observable, путем применения функции к каждому из них:</p><figure><img src="https://media.tproger.ru/uploads/2016/10/map.png" alt="" /></figure><p>Хорошей иллюстрацией возможностей ReactiveX является приложение RSS-агрегатора. Здесь возникает необходимость асинхронной загрузки данных, фильтрации и трансформации значений, поддержания актуального состояния путем периодического обновления.</p><p>В этой статье примеры для представления основных принципов ReactiveX написаны с использованием библиотеки <a href="https://github.com/ReactiveX/RxPY">rx</a> для языка программирования Python. Вот так, например, выглядит абстрактная реализация наблюдателя:</p><p>Наше приложение в режиме реального времени будет обмениваться сообщениями с браузером посредством веб-сокетов. Возможность легко реализовать это предоставляет <a href="http://www.tornadoweb.org/">Tornado</a>.</p><p>Работа программы начинается с запуска сервера. При обращении браузера к серверу открывается веб-сокет.</p><p>Для обработки введенного пользователем запроса создается Subject, при подписке на который он отправляет значение по умолчанию (в нашем случае — пустую строку), а затем раз в секунду отправляет то, что введено пользователем и удовлетворяет условиям: длина 0 или больше 2, значение изменилось.</p><p>Также для периодического обновления новостей предусмотрен Observable, который раз в 60 секунд отдает значение.</p><p>Два этих потока соединяются оператором combine_latest , в цепочку встраивается Observable для получения списка новостей. После чего на этот Observable создается подписка, вся цепочка начинает работать только в этот момент.</p><p>Следует подробнее остановиться на том, что такое “Observable для получения списка новостей”. Из списка url для получения новостей мы создаем поток данных, элементы которого приходят в функцию, где при помощи HTTP-клиента Tornado AsyncHTTPClient происходит асинхронная загрузка данных для каждого элемента списка urls. Из них также создается поток данных, который фильтруется по запросу, введенному пользователем. Из каждого потока мы берем по 5 новостей, которые приводим к нужному формату для отправки на фронтенд.</p><p>После того, как поток выходных данных сформирован, его подписчик начинает поэлементно получать данные. Функция send_response  отправляет полученные значения во фронтенд, который добавляет новость в список.</p><p>В файле feeder.js</p><p>Таким образом, реализуется push-технология, в которой данные поступают от сервера к фронтенду, который лишь отправляет введенный пользователем запрос для поиска по новостям.</p><p>В качестве заключения предлагаю задуматься о том, какая реализация получилась бы при привычном подходе с использованием колбэков вместо Observable, без возможности легко объединить потоки данных, без возможности мгновенной отправки данных потребителю-фронтенду и с необходимостью отслеживать изменения в строке запроса. Среди Python-разработчиков технология пока что практически не распространена, однако я вижу уже несколько возможностей её применить на текущих проектах.</p><p>Пример использования ReactiveX для Python вы можете найти в <a href="https://github.com/noveogroup/rxpy-example">GitHub репозитории</a> с демо-проектом RSS-агрегатора.</p><p>Выражаем благодарность Ксении, Python-разработчику компании Noveo, за подробное описание преимуществ реактивного программирования!</p>]]></content:encoded>
    </item>
  </channel>
</rss>