Я полагаю, вы будете полны любопытства при изучении новой технологии реактивного программирования, особенно некоторых ее вариантов, таких как: серия Rx, Bacon.js, RAC и т. д.
В отсутствие отличных материалов процесс обучения реактивному программированию будет полон терний. Сначала я попытался найти несколько руководств, но нашел лишь несколько практических руководств, и они были очень простыми, и никто не взялся за создание полного свода знаний о реактивном программировании. Кроме того, официальная документация обычно не очень хорошо помогает разобраться в некоторых функциях, потому что обычно они выглядят запутанно, если не верите, посмотрите здесь:
Rx.Observable.prototype.flatMapLatest(selector, [thisArg])
В соответствии с нижним индексом элемента сопоставьте каждый элемент в наблюдаемой последовательности с новой наблюдаемой последовательностью один за другим, а затем...%…………%&$#@@…&** (головокружение)
Боже мой, это так запутанно!
Я прочитал две связанные книги, одна просто рисует вам отличную картину реактивного программирования, а другая просто копается в том, как использовать реактивные библиотеки. Я немного узнал о реактивном программировании в процессе постоянного создания проектов и, наконец, изучил реактивное программирование таким трудным путем. Я использую его в реальном проекте в компании, в которой работаю, а также получаю поддержку от коллег, когда сталкиваюсь с проблемами.
Самая трудная часть процесса обучения — научиться думать реактивно, а это значит, что нужно отказаться от типичных старых императивных привычек и привычек программирования с отслеживанием состояния и заставить свой мозг работать в другой парадигме. Я не нашел в Интернете ни одного учебника, который анализировал бы его с этого уровня, и я думаю, что мир заслуживает хорошего практического руководства, которое научит вас думать в реактивном программировании и поможет вам начать изучение реактивного программирования. Затем просмотрите документацию по различным библиотекам, чтобы получить дополнительные рекомендации. Надеюсь, эта статья помогла вам быстро окунуться в мир реактивного программирования.
«Что такое реактивное программирование?»
В сети есть целая куча плохих объяснений и определений, напримерWikipediaобычно очень общие и теоретические объяснения, в то время какStackoverflowНекоторые из приведенных выше канонических ответов явно не подходят для новичков.Reactive ManifestoТакже похоже на что-то, что можно показать вашему премьер-министру или боссу, MicrosoftRx-терминология«Rx = Observables + LINQ + Schedulers» также слишком тяжел и полон слишком большого количества вещей Microsoft, что вносит еще больше путаницы. Такие термины, как «реактивный» и «распространение изменений», не передают каких-либо осмысленных понятий, связанных с используемой вами структурой MV* и вашим любимым языком программирования. Конечно, моя рамка просмотра может реагировать на модель, и, конечно же, мои изменения распространяются, без этого мой интерфейс вообще ничего не отображал бы.
Так что хватит нести чушь.
Реактивное программирование — это парадигма программирования, которая взаимодействует с асинхронными потоками данных.
С одной стороны, в этом нет ничего нового. Шины событий или некоторые типичные события кликов по сути являются асинхронным потоком событий, поэтому вы можете наблюдать за его изменением и вызывать побочные эффекты. Реактивность — это идея о том, что вы можете создать поток данных для чего угодно, кроме событий щелчка и наведения. Потоки данных есть везде, что угодно может быть потоком данных, например, переменные, пользовательский ввод, свойства, кеши, структуры данных и т. д. Например, вы можете думать о своей функции подписки Weibo как о потоке данных, таком как событие клика, вы можете прослушивать такой поток данных и реагировать соответствующим образом.
Кроме того, у вас будет несколько замечательных функций для объединения, создания и фильтрации любого набора потоков данных. Это магия «функционального программирования». Один поток данных может быть входом для другого потока данных, и даже несколько потоков данных могут быть входом для другого потока данных. Ты сможешьсливатьсяДва потока данных также могут бытьфильтрОдин поток получает другой поток, содержащий только интересующие вас события, и вы можетекартаПоток значений в новый поток.
Поток данных является ядром всей системы реактивного программирования.Если вы хотите изучить реактивное программирование, конечно, вы должны сначала войти в поток данных, чтобы выяснить это. Итак, давайте начнем со знакомого потока событий «нажмите кнопку».
Поток данных представляет собой последовательность текущих событий, упорядоченных во времени. Как показано выше, он может генерировать 3 разных события (в предыдущем предложении они были названы событиями): событие значения некоторого типа, событие ошибки и событие завершения. Когда происходит событие завершения, в некоторых случаях мы можем сделать что-то вроде этого: закрыть окно или компонент представления, содержащий эту кнопку.
Мы можем захватывать только те события, которые генерируются асинхронно, что позволяет нам выполнять одну функцию, когда генерируется событие значения, одну функцию, когда генерируется событие ошибки, и другую функцию, когда генерируется событие done. Иногда вы можете игнорировать последние два события и просто сосредоточиться на том, как определить и спроектировать функцию, которая будет выполняться, когда генерируется событие значения.Процесс прослушивания этого потока событий называется подпиской, функция, которую мы определяем, называется наблюдателем, и поток событий может называться наблюдаемым субъектом (или называться наблюдаемым). Вы должны были заметить, да, этоШаблон наблюдателя.
Приведенную выше схематическую диаграмму также можно перерисовать в коде ASCII.Обратите внимание, что мы продолжим использовать это изображение в некоторых из следующих руководств:
--a---b-c---d---X---|->
a, b, c, d 是值事件
X 是错误事件
| 是完成事件
---> 是时间线(轴)
скопировать код
Теперь, когда вы знакомы с потоками событий реактивного программирования, давайте попробуем кое-что новое, чтобы вам не было скучно: мы создадим новый поток событий кликов, который эволюционирует из исходного потока событий кликов.
Во-первых, давайте создадим поток событий, который записывает нажатия кнопок. В обычных реактивных библиотеках к каждому потоку событий будут прикреплены некоторые функции, напримерmap,filter, scanи т. д., когда вы вызываете один из этих методов, напримерclickStream.map(f), который возвращает новый поток событий на основе потока событий кликов. Он не вносит никаких изменений в исходный поток событий щелчка. Эта функция называется неизменностью, и ее можно сочетать с реактивными потоками событий, такими как соевое молоко и оладьи. Таким образом, мы можем использовать связанные функции для вызова, например:clickStream.map(f).scan(g):
clickStream: ---c----c--c----c------c-->
vvvvv map(c becomes 1) vvvv
---1----1--1----1------1-->
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->
скопировать код
map(f)Функция будет основана на том, что вы предоставляетеfФункция сопоставляет каждое возвращаемое значение в исходном потоке событий с новым потоком событий. В приведенном выше примере мы сопоставляем каждому событию клика число 1,scan(g)Функция агрегирует ранее сопоставленные значения, а затемx = g(accumulated, current)алгоритм, чтобы справиться с этим соответствующим образом, иgФункция на самом деле является простой функцией сложения. Затем, когда происходит событие щелчка,counterStreamФункция сообщает текущее общее количество событий кликов.
Чтобы продемонстрировать настоящую магию реактивного программирования, давайте предположим, что у вас есть поток событий «двойной щелчок», и, чтобы сделать его более интересным, давайте предположим, что этот поток событий обрабатывает события как «тройного щелчка», так и «множественного щелчка», и рассмотрим более подробно. дыхание Подумайте о том, как сделать это традиционным императивным способом и с сохранением состояния, и я держу пари, что это было бы довольно неприятно, включая некоторые переменные для хранения состояния и некоторые корректировки временных интервалов.
Метод реактивного программирования будет очень кратким, на самом деле, часть логической обработки требует толькочетыре строки кода. Тем не менее, давайте пока проигнорируем часть кода на этом этапе, независимо от того, новичок вы или эксперт, размышления о диаграммах для понимания и построения потока событий будут отличным способом сделать это.
На рисунке серым прямоугольником показан функциональный процесс преобразования верхнего потока событий в нижний поток событий.Во-первых, по интервалу 250 мс (событие молчание, Примечание переводчика: период времени, когда не происходит никакого события, предыдущее событие происходит к следующему событию) Интервал между появлениями) разделяет поток событий кликов сегмент за сегментом, а затем добавляет одно или несколько событий кликов из каждого сегмента в список (это функция:buffer(stream.throttle(250ms))То, что мы делаем, мы не спешим разбираться в деталях, нам просто нужно сначала сосредоточиться на отзывчивой части). Теперь мы получаем несколько списков с потоками событий и используемmap()Функция для вычисления целочисленного значения каждой длины списка для сопоставления со следующим потоком событий. Наконец, мы использовали фильтрfilter(x >= 2)функция игнорирует менее1целое число . Таким образом, мы использовали 3 шага для создания нужного нам потока событий.Далее мы можем подписаться («прослушать») на это событие и делать то, что хотим.
Я надеюсь, вы можете почувствовать элегантность этого примера. Конечно, этот пример — лишь верхушка айсберга магии реактивного программирования, вы также можете применить эти 3 шага к разным видам потоков событий, например, к потоку ответов API. С другой стороны, у вас все еще есть масса функций, которые вы можете использовать.
«Почему я должен использовать реактивное программирование?»
Реактивное программирование может повысить степень абстракции вашего кода, позволяя вам больше сосредоточиться на определении бизнес-логики, которая взаимозависима с событиями, а не на деталях реализации.В то же время использование реактивного программирования также может изменить ваш код. более лаконичным.
Особенно для популярных веб-приложений и мобильных приложений, чьи события пользовательского интерфейса и данные часто взаимодействуют, преимущества использования реактивного программирования при разработке этих приложений будут более очевидными. Десять лет назад взаимодействие с веб-страницами заключалось в отправке длинной формы данных на серверную часть, а затем выполнении некоторых простых операций рендеринга во внешнем интерфейсе. Теперь приложения эволюционировали, чтобы работать в режиме реального времени: простое изменение одного поля формы может автоматически активировать код, сохраненный в серверной части, точно так же, как пользователю нравится какой-либо контент, это может быть отражено в режиме реального времени для других подключенных пользователей и т. д. .
Современные приложения изобилуют событиями в реальном времени, чтобы обеспечить эффективное взаимодействие с пользователем, нам нужно использовать подходящий инструмент, чтобы справиться с этим, тогда реактивное программирование — это именно то, что нам нужно.
Примеры мышления в реактивном программировании
Давайте погрузимся в некоторые реальные примеры, которые шаг за шагом научат вас думать в реактивном программировании, без выдуманных примеров, без полупонятных концепций. В конце этого урока мы сгенерируем некоторый реальный код функции и сможем узнать, почему каждый шаг делает это (зная это, зная, почему).
Я выбрал JavaScript иRxJSбыть языком программирования для этого руководства, потому что: JavaScript, безусловно, самый знакомый язык, в то время какБиблиотеки для серии RxОн очень широко используется для многих языков и платформ, таких как (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa,Groovyи Т. Д. Таким образом, независимо от того, какой язык, библиотеку или инструмент вы используете, вы можете учиться (и получать пользу) от этого руководства ниже.
Внедрите реферальную подписку (Who следовать) функция
В Twitter есть элемент пользовательского интерфейса, который рекомендует пользователей, на которых вы можете подписаться, как показано ниже:
Мы сосредоточимся на имитации его основных функций, а именно:
- В начале данные учетной записи пользователя рекомендуемого подписчика загружаются из API, а затем отображаются три рекомендуемых пользователя.
- Нажмите «Обновить», чтобы загрузить еще трех рекомендованных пользователей в текущие три строки.
- Нажмите кнопку «x» для рекомендуемого пользователя в каждой строке, очистите текущего пользователя и отобразите нового пользователя в текущей строке.
- В каждой строке отображается аватар пользователя, и при нажатии на нее можно перейти на домашнюю страницу.
Мы можем оставить другие функции и кнопки в покое, потому что они второстепенны. Поскольку Твиттер недавно отключил несанкционированные вызовы общедоступного API, мы будем использоватьGithub API для привлечения пользователейвместо этого и создайте наш пользовательский интерфейс соответствующим образом.
Если вы хотите сначала увидеть окончательный эффект, вот готовыйкод.
Запрос и ответ
Как эта проблема решается в Rx? , прежде чем мы начнем, нам нужно понять, что (почти)Все может быть потоком событий, что является мантрой Rx. Начнем с самой простой функции: «Начальная фаза, загрузите рекомендуемые следующие данные учетной записи пользователя из API, затем отобразите трех рекомендуемых пользователей». На самом деле ничего особенного в этой функции нет, простые шаги делятся на: (1) отправить запрос, (2) получить данные ответа, (3) отобразить данные ответа. хорошо, давайте рассматривать запрос как поток событий, вы можете сначала подумать, что это немного преувеличено, но не волнуйтесь, мы тоже должны начать с основ, не так ли?
В начале нам нужно только сделать запрос, если мы используем его как поток данных, это может быть только поток событий, который просто возвращает значение. Через некоторое время у нас будет еще много запросов, но пока есть только один.
--a------|->
a就是字符串:'https://api.github.com/users'
скопировать код
Это поток событий URL, которые мы хотим запросить. Всякий раз, когда возникает запрос, он сообщает нам две вещи: когда и что. Когда запрос выполняется, когда генерируется событие. И делается то, что запрошено, то есть запрошенная строка URL.
В Rx создать поток событий, который возвращает значение, очень просто. На самом деле термин поток событий в Rx называется «наблюдаемый», что означает, что его можно наблюдать, но я нашел это название глупым, поэтому я предпочитаю называть егопоток событий.
var requestStream = Rx.Observable.just('https://api.github.com/users');
Но теперь это просто поток событий строки, и никакие другие операции не выполняются, поэтому нам нужно выполнить некоторые операции, которые мы хотим выполнить при генерировании этого значения, что можно сделать с помощьюПодпискаэто событие для достижения.
requestStream.subscribe(function(requestUrl) {
// execute the request
jQuery.getJSON(requestUrl, function(responseData) {
// ...
});
}
Обратите внимание, что здесь мы используем метод обратного вызова JQuery AJAX (мы предполагаем, что выУже хорошо знаете JQuery и AJAX) для обработки этой операции асинхронного запроса. Но постойте, Rx предназначен для обработки асинхронных потоков данных, разве он не может обрабатывать потоки данных из запросов, которые отвечают в какой-то момент в будущем? Ну, это теоретически возможно, давайте попробуем.
requestStream.subscribe(function(requestUrl) {
// execute the request
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUrl)
.done(function(response) { observer.onNext(response); })
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
});
responseStream.subscribe(function(response) {
// do something with the response
});
}
Rx.Observable.create()Операция заключается в создании собственного пользовательского потока событий, а для событий данных (onNext()) и события ошибок (onError()) будет отображать уведомление для каждого наблюдателя (или подписчика) о событии. Все, что мы делаем, это небольшая оболочка вокруг jQuery.
Ajax Promise — это именно то, что нужно. Подождите, значит ли это, что jQuery Ajax Promise по сути является Observable?
да.
Promise++ является наблюдаемым (Observable), в Rx вы можете использовать эту операцию:var stream = Rx.Observable.fromPromise(promise), вы можете легко преобразовать Promise в Observable, и очень простая операция позволяет нам начать использовать его прямо сейчас. Разница в том, что ни одна из этих наблюдаемых несовместимаPromises/A+, но теоретически не конфликтует. Промис — это простой наблюдаемый объект с одним возвращаемым значением, а Rx — это гораздо больше, чем промис, который позволяет возвращать несколько значений.
Это лучше, это подчеркивает, что Observables, по крайней мере, более мощны, чем Promises, поэтому, если вы верите в то, что рекламируют Promises, то также обратите внимание на то, что может сделать реактивное программирование.
Теперь вернемся к примеру, вы должны быстро увидеть, что мы находимся вsubscribe()Внутренняя часть метода вызывается сноваsubscribe()метод, который чем-то похож на callback hell, иresponseStreamСоздание также зависит отrequestStreamиз. Ранее мы говорили, что в Rx есть много очень простых механизмов преобразования из других потоков событий и создания новых потоков событий, поэтому мы должны попытаться сделать то же самое.
Одна из самых основных функций, которые вам нужно знать сейчас, этоmap(f), который может принимать каждое значение из потока событий A и выполняться для каждого значенияf()функцию, а затем заполнить поток событий B полученным новым значением. Применительно к нашим потокам событий запросов и ответов мы можем сопоставить URL-адрес запроса с промисами ответа (маскируясь под поток данных).
var responseMetastream = requestStream
.map(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
Затем мы создалиmetastream«Монстр: поток событий, загруженный потоком событий. Не паникуйте, метапоток — это поток событий, где каждое испускаемое значение — это другой поток событий, думайте об этом как о [указателях]((En. Wikipedia.org/wiki/pointe…
Отзывчивый метапоток, который кажется запутанным, похоже, совсем нам не помогает. Нам просто нужен простой поток данных ответа, где каждое испускаемое значение является простым объектом JSON, а не объектом JSON «Promise». хорошо, давайте посмотрим на другую функцию:Flatmap,этоmap()Еще одна версия функции, более плоская, чем метастрим. Все, что генерируется в потоке событий «основного тела», будет испускаться в потоке событий «ветви». Flatmap не является исправлением для метапотоков, и метапотоки не являются ошибкой. Оба они являются хорошими инструментами и помощниками для обработки событий асинхронного ответа в Rx.
var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
Отлично, потому что наш поток событий ответа определяется в соответствии с потоком событий запроса, и если в будущем у нас будет больше событий в потоке событий запроса, мы также будем получать события ответа в соответствующем потоке событий ответа, как и ожидалось, вот так:
requestStream: --a-----b--c------------|->
responseStream: -----A--------B-----C---|->
(小写的是请求事件流, 大写的是响应事件流)
скопировать код
Теперь у нас, наконец, есть отзывчивый поток событий, и мы можем отображать полученные данные:
responseStream.subscribe(function(response) {
// render `response` to the DOM however you wish
});
Давайте соберем весь код вместе и посмотрим:
var requestStream = Rx.Observable.just('https://api.github.com/users');
var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
responseStream.subscribe(function(response) {
// render `response` to the DOM however you wish
});
кнопка обновления
Я не упомянул, что данные JSON этого ответа представляют собой список, содержащий пользовательские данные 100. Этот API позволяет указать только смещение страницы (смещение страницы), но не размер страницы (размер страницы), мы используем только 3 1 пользовательских данных а остальные 97 тратятся впустую, вы можете пока игнорировать эту проблему, позже мы научимся кэшировать данные ответа.
Всякий раз, когда нажимается кнопка обновления, поток событий запроса выдает новое значение URL, чтобы мы могли получить новые данные ответа. Здесь нам нужны две вещи: поток событий нажатия кнопки обновления (критерии: в качестве потока событий можно использовать все) и нам нужно сделать поток событий нажатия кнопки обновления зависимостью потока событий запроса (то есть , щелчок по потоку событий обновления вызовет поток событий запроса). К счастью, в RxJS уже есть способ преобразования прослушивателей событий в наблюдаемые.
var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
Поскольку событие нажатия кнопки обновления не содержит URL-адрес запрашиваемого API, нам нужно сопоставить каждый клик с фактическим URL-адресом. Теперь мы преобразуем поток событий запроса в поток событий кликов и преобразуем карты каждого клика в случайную страницу. параметр смещения для составления URL-адреса API.
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
Я просто испортил функцию, которую сделал ранее, потому что я тупой и не использую автоматические тесты. Таким образом, запрос не будет выполняться в начале, а только тогда, когда произойдет событие клика. Нам нужно, чтобы выполнялись оба случая: запрос, который выполняется как при первом открытии страницы, так и при нажатии кнопки обновления.
Мы умеем делать отдельный поток событий для каждого случая:
var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
Но можно ли совместить эти два в одном? Да, да, мы можем использоватьmerge()метод достижения. Следующий рисунок может объяснитьmerge()Полезность функции:
stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
vvvvvvvvv merge vvvvvvvvv
---a-B---C--e--D--o----->
скопировать код
Это должно быть просто сделать сейчас:
var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
var requestStream = Rx.Observable.merge(
requestOnRefreshStream, startupRequestStream
);
Существует также более чистый способ написать это, опуская промежуточную переменную потока событий:
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
})
.merge(Rx.Observable.just('https://api.github.com/users'));
Он может быть еще короче и читабельнее:
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
})
.startWith('https://api.github.com/users');
startWith()Функция делает именно то, что вы ожидаете. Каким бы ни был ваш входной поток событий, используйтеstartWith(x)Выход потока событий после обработки функции должен бытьxрезультат в начале. Но я не всегдаДублирующийся код (СУХОЙ), я просто повторяю строку URL для API, было бы лучше добавитьstartWith()функция перемещена вrefreshClickStreamТам вы можете имитировать событие нажатия кнопки обновления при запуске.
var requestStream = refreshClickStream.startWith('startup click')
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
Что ж, если вы вернетесь к «Сломанному автотесту» и сравните их, вы увидите, что я только что добавил одинstartWith()только функция.
Моделирование 3 рекомендуемых пользовательских данных с потоками событий
До сих пор в потоке событий ответа (responseStream) подписка (subscribe()) происходит на этапе рендеринга, мы лишь кратко упоминаем об этомРекомендуемое вниманиеПользовательский интерфейс. Теперь, когда у нас есть кнопка обновления, у нас возникнет проблема: когда вы нажимаете кнопку обновления, текущие три рекомендуемых пользователя-последователя не ясны, и пока данные ответа достигнуты, мы получим новые данные рекомендуемого пользователя-подписчика. , Чтобы сделать пользовательский интерфейс более красивым, нам нужно знать трех текущих рекомендуемых пользователей, которым следует следовать, когда происходит нажатие кнопки обновления.
refreshClickStream.subscribe(function() {
// clear the 3 suggestion DOM elements
});
Нет, чувак, не так быстро. У нас снова возникла новая проблема, потому что теперь у нас есть два подписчика, влияющие на элемент UI DOM, который рекомендует фокус (другойresponseStream.subscribe()), что не соответствуетРазделение интересовПринципы, помните принципы реактивного программирования?
Теперь давайте смоделируем рекомендуемые пользовательские данные для отслеживания в виде потока событий, где каждое испускаемое значение представляет собой объект JSON, содержащий рекомендуемые пользовательские данные для отслеживания. Мы будем обрабатывать эти три пользовательских данных отдельно.Следующий поток событий пользовательских данных № 1 рекомендуется обратить внимание:
var suggestion1Stream = responseStream
.map(function(listUsers) {
// get one random user from the list
return listUsers[Math.floor(Math.random()*listUsers.length)];
});
Другие, такие как поток событий пользовательских данных пользователя № 2, рекомендуемых к вниманию.suggestion2Streamи событийный поток пользовательских данных №3 рекомендуется соблюдатьsuggestion3Streamможно легко получить доступ изsuggestion1StreamПросто скопируйте и вставьте. Мы не повторяем код здесь, просто чтобы сделать наш пример проще, и я думаю, что это хороший повод подумать о том, как избежать повторения кода.
Instead of having the rendering happen in responseStream's subscribe(), we do that here:
suggestion1Stream.subscribe(function(suggestion) {
// render the 1st suggestion to the DOM
});
Мы не обрабатываем рендеринг в методе subscribe() в responseStream, мы обрабатываем его следующим образом:
suggestion1Stream.subscribe(function(suggestion) {
// render the 1st suggestion to the DOM
});
Возвращаясь к «При обновлении очистить текущих рекомендуемых пользователей для подписки», мы можем просто сопоставить щелчок обновления с отсутствием рекомендуемых данных (nullданные предложения), и вsuggestion1Streamвключаются следующим образом:
var suggestion1Stream = responseStream
.map(function(listUsers) {
// get one random user from the list
return listUsers[Math.floor(Math.random()*listUsers.length)];
})
.merge(
refreshClickStream.map(function(){ return null; })
);
При рендеринге мы будемnullИнтерпретируйте это как «нет данных» и скройте элемент пользовательского интерфейса.
suggestion1Stream.subscribe(function(suggestion) {
if (suggestion === null) {
// hide the first suggestion DOM element
}
else {
// show the first suggestion DOM element
// and render the data
}
});
Теперь наша грубая схема выглядит следующим образом:
refreshClickStream: ----------o--------o---->
requestStream: -r--------r--------r---->
responseStream: ----R---------R------R-->
suggestion1Stream: ----s-----N---s----N-s-->
suggestion2Stream: ----q-----N---q----N-q-->
suggestion3Stream: ----t-----N---t----N-t-->
скопировать код
Nпредставлятьnull
В качестве дополнения мы можем выдать пустые рекомендации в начале. Это делается путем добавления startWith(null) в рекомендуемый поток событий:
var suggestion1Stream = responseStream
.map(function(listUsers) {
// get one random user from the list
return listUsers[Math.floor(Math.random()*listUsers.length)];
})
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
Результат таков:
refreshClickStream: ----------o---------o---->
requestStream: -r--------r---------r---->
responseStream: ----R----------R------R-->
suggestion1Stream: -N--s-----N----s----N-s-->
suggestion2Stream: -N--q-----N----q----N-q-->
suggestion3Stream: -N--t-----N----t----N-t-->
скопировать код
Рекомендуется пристальное внимание и использование кэшированных ответов.
Только эта функция остается нереализованной, каждый рекомендуемый пользовательский интерфейс будет иметь кнопку «x», чтобы закрыть себя, а затем загрузить другого рекомендуемого пользователя в текущий пользовательский интерфейс данных. Первоначальная идея заключалась в следующем: необходимо сделать новый запрос при нажатии любой кнопки закрытия:
var close1Button = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');
// and the same for close2Button and close3Button
var requestStream = refreshClickStream.startWith('startup click')
.merge(close1ClickStream) // we added this
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
Это не имеет никакого эффекта, он закрывается и перезагружаетсявсеРекомендации ориентированы на пользователя, а не только на того, кто обрабатывает наши клики. Вот несколько способов решить эту проблему, и, чтобы сделать ее интересной, мы будем повторно использовать данные предыдущего запроса для решения этой проблемы. Размер данных на страницу, на которую отвечает этот API, составляет 100 пользовательских данных, и мы используем только три из них, поэтому существует много неиспользуемых данных, которые можно использовать без запроса дополнительных данных.
Хорошо, снова давайте продолжим думать с точки зрения потоков событий. Когда происходит событие click close1, мы хотим использоватьнедавно выпущенныйданные ответа и выполнитьresponseStreamфункция для случайного извлечения пользовательских данных из списка ответов, например:
requestStream: --r--------------->
responseStream: ------R----------->
close1ClickStream: ------------c----->
suggestion1Stream: ------s-----s----->
скопировать код
В Rx функция композиции называетсяcombineLatest, должно быть то, что нам нужно. Эта функция принимает поток данных A и поток данных B в качестве входных данных, и независимо от того, какой поток данных выдает значение,combineLatestФункция возьмет самое последнее значение, полученное из двух потоков данных.aиbв видеfВходные данные функции, которая вычисляется и возвращает выходное значение (c = f(x,y)), следующая диаграмма сделает процесс этой функции более понятным:
stream A: --a-----------e--------i-------->
stream B: -----b----c--------d-------q---->
vvvvvvvv combineLatest(f) vvvvvvv
----AB---AC--EC---ED--ID--IQ---->
f是转换成大写的函数
скопировать код
Таким образом, мы можем положитьcombineLatest()функция используется вclose1ClickStreamи responseStreamOn, пока нажата кнопка закрытия, мы можем получить самые последние данные ответа, а вsuggestion1Streamдля создания нового значения. с другой стороны,combineLatest()Функции также относительны: всякий раз, когда вresponseStreamопубликовать новый ответ на点击关闭按钮事件для создания новых рекомендуемых пользовательских данных для отслеживания, что очень интересно, потому что это может дать нашимsuggestion1StreamУпрощенный код:
var suggestion1Stream = close1ClickStream
.combineLatest(responseStream,
function(click, listUsers) {
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
Сейчас нам не хватает небольшого кусочка головоломки.combineLatest()Функция использует два самых последних источника данных, но если один из источников данных ничего не выдал,combineLatest()Функция не может генерировать событие данных в выходном потоке. Если вы посмотрите на диаграмму ASCII выше (первая диаграмма в статье), вы поймете, что когда первый поток данных выдает значениеaвывода нет, только когда второй поток выдает значениеbбудет генерировать выходное значение.
Есть много способов решить эту проблему, мы используем самый простой, который заключается в имитации события щелчка «закрыть 1» при запуске:
var suggestion1Stream = close1ClickStream.startWith('startup click') // we added this
.combineLatest(responseStream,
function(click, listUsers) {l
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
инкапсулировать
Мы закончили, вот полный инкапсулированный пример кода:
var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
var closeButton1 = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
// and the same logic for close2 and close3
var requestStream = refreshClickStream.startWith('startup click')
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var responseStream = requestStream
.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
});
var suggestion1Stream = close1ClickStream.startWith('startup click')
.combineLatest(responseStream,
function(click, listUsers) {
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
// and the same logic for suggestion2Stream and suggestion3Stream
suggestion1Stream.subscribe(function(suggestion) {
if (suggestion === null) {
// hide the first suggestion DOM element
}
else {
// show the first suggestion DOM element
// and render the data
}
});
ты сможешьздесьпосмотреть демонстрационный проект
Вышеприведенный фрагмент кода небольшой, но делает многое: он управляет несколькими потоками событий, используя принцип надлежащего разделения задач, и даже кэширует данные ответов. Этот функциональный стиль делает код больше похожим на декларативное программирование, чем на императивное: мы не даем набор инструкций для выполнения, мы просто определяем взаимосвязь между потоком событий, чтобы сообщить ему, что это такое. Например, мы используем Rx, чтобы сообщить компьютеруsuggestion1Streamэто событие «закрыть 1» в сочетании с потоком пользовательских данных, полученных из последних данных ответа, в дополнение к этому, когда происходит событие обновления и запускается программа, этоnull.
Обратите внимание, что код не отображается, напримерif, for, whileИ т. д. операторы управления потоком или типичное управление потоком на основе обратного вызова, такое как JavaScript. Если вы можете (позже я оставлю вам некоторые детали реализации в качестве упражнения), вы даже можете использоватьsubscribe()использовать наfilter()функция избавления отifиelse. В Rx у нас есть, например:map, filter, scan, merge, combineLatest, startWithТакие функции, как поток данных, существует множество функций, которые можно использовать для управления программированием, управляемым событиями (программирование, управляемое событиями).
программа) процесс. Набор этих функций позволяет делать больше с меньшим количеством кода.
следующий
Если вы считаете, что Rx станет вашей библиотекой реактивного программирования, найдите время, чтобы ознакомиться с ней.большое количество функцийИспользуется для преобразования, объединения и создания наблюдаемых объектов. Если вы хотите ознакомиться с этими функциями в графе потока событий, взгляните на это:RxJava's very useful documentation with marble diagrams. Помните: всякий раз, когда у вас возникает проблема, вы можете нарисовать эти диаграммы, подумать над ней, посмотреть на этот длинный список функций и продолжать думать. По моему личному опыту, это работает очень хорошо.
Как только вы начнете программировать с помощью Rx, помните, поймитеCold vs Hot ObservablesКонцепция очень необходима, если вы проигнорируете это, она отскочит назад и жестоко укусит вас. Я предупреждал вас здесь, изучение функционального программирования может улучшить ваши навыки, ознакомиться с некоторыми распространенными проблемами, такими как побочные эффекты Rx
Но библиотеки реактивного программирования — это не только Rx, есть относительно простые для понимания без причуд Rx.Bacon.js.Elm Languageзатем по-своему поддерживает реактивное программирование: это реактивный язык программирования, который компилируется в Javascript + HTML + CSS и имеетtime travelling debuggerфункция, это здорово.
И RX отлично подходит для тяжелых программиров, таких как Frontend и Apps. Но его можно использовать не только на стороне клиента, но и на бэкэнде или где-то рядом с базой данных. По факту,RxJava — это компонент, используемый API-интерфейсом сервера Netflix для обработки параллелизма.. Rx — это не фреймворк, ограниченный определенным приложением или языком программирования, это действительно отличная парадигма программирования, которой вы можете следовать при написании любой программы, управляемой событиями.
Что такое неудача? Ничего, всего один шаг к успеху, что такое успех? После всех дорог, ведущих к неудачам, остается только одна дорога — дорога к успеху.