Я был свидетелем многих вопросов, связанных с Subject`ами на Stack Overflow. Недавно я увидел одного разработчика, который интересовался, как, собственно говоря, работает AsyncSubject. Вопрос заставил меня написать эту статью, чтобы показать, почему необходимо использовать различные типы Subject`ов и как их использовать.
Когда мы используем Subject`ы?
В своей статье Бен Леш утверждал, что:
… [мультикастинг] является основной причиной использования Subject`ов в RxJS.
Что касательно мультикастинга, мы рассмотрим его более подробно немного позже. Сейчас же нам достаточно знать, что он позволяет принимать оповещения от одной «наблюдаемости» и отправлять их другим «наблюдателям».
Подобная связь «наблюдаемости» с «наблюдаемыми» и есть сутью Subject`ов. Причина этого заключается в том, что де-факто Subject`ы являются одновременно «наблюдаемостью» и «наблюдателями».
Как они могут быть использованы?
В качестве примера давайте рассмотрим компонент Angular awesome-component. Наш компонент выполняет определенную работу и содержит в себе внутреннюю «наблюдаемость», что производит определенное значение при работе с ней пользователя.
Чтобы позволить родительским компонентам получить доступ к «наблюдаемости», awesome-component принимает «наблюдателя», что вводит свойство и что подписывается, в свою очередь, на «наблюдаемость». Это значит, что отныне родитель может соединиться с «наблюдаемостью» при помощи спецификации «наблюдателя» - что-то наподобие этого:
Так как теперь «наблюдатель» «обвязан», родитель соединен и получает значения от awesome-component. Впрочем по сути это то же самое, как если бы awesome-component производил значения через подписанные события. Так почему же мы здесь не используем события?
Дело в том, что «наблюдаемостями» проще управлять. К примеру, чтобы добавить фильтры, нам необходимо использовать лишь несколько операторов. Но немаловажный нюанс: родительский компонент имеет «наблюдателя» – не «наблюдаемость», так как в таком случае мы можем применять операторы?
Subject`ы - это одновременно и «наблюдаемости», и «наблюдатели», поэтому, когда мы создаем Subject, он может быть использован по отношению к awesome-component в качестве «наблюдателя» или работать с компонентом как с «наблюдаемостью». Что-то наподобие этого:
Subject соединяет «наблюдателя» по принципу «делай-все-что-хочешь-со-значением» с «наблюдаемостью» в виде awesome-component. Но здесь применяется набор операторов родителей компонента.
Композиция различных «наблюдаемостей»
При помощи Subject`а для композиции «наблюдаемости» awesome-component может быть использован в разных целях разными компонентами. К примеру, другой компонент может быть заинтересован только в последнем сгенерированном значении. Для этого нужно использовать last-оператор:
Что интересно, это не единственный способ получения последнего значения: мы просто можем использовать другой Subject. К примеру, при помощи AsyncSubject код будет выглядеть следующим образом, так как он производит только последнее полученное значение:
Но, если использование AsyncSubject равнозначно композиции «наблюдаемости» при помощи использования Subject и last-оператора, зачем усложнять RxJS лишним классом?
Ну, в основном потому что Subject`ты предназначены для мультикастинга.
В данном случае два способа эквивалентны, потому что здесь есть только один подписчик. В ситуации с применением мультикастинга здесь было бы несколько подписчиков и применять оператор last здесь было бы нецелесообразно.
Теперь же давайте рассмотрим мультикастинг более детально.
Как Subject`ы используются непосредственно в RxJS?
Ядро инфраструктуры мультикастинга RxJS исполняется при помощи оператора multicast. Multicast вообще применяется к ключевым «наблюдаемостям», принимает Subject и возвращает полученную из Subject`а «наблюдаемость».
Оператор multicast чем-то похож на awesome-component. Мы можем принимать «наблюдаемость», чье поведение зависит от принимаемого Subject`а.
Видео курсы по схожей тематике:
Ситуации, когда базовый Subject передается multicast:
- Подписчики мультикаст-«наблюдаемости» принимают оповещения типа next, error, complete.
- «Поздние» подписчики , другими словами, те, которые подписались после оповещений error, complete, – так же в свою очередь принимают эти оповещения.
Важно отметить, что пока мультикастинг не передал factory, «поздние» подписчики не работают с другими подписками на источник.
Чтоы произвести композицию по отношению к мультикаст-«наблюдаемости», что передает последнее оповещение next ко всем подписчикам, недостаточно просто применить last-оператор к «наблюдаемости», созданной при помощи Subject. «Поздние» подписчики подобной «наблюдаемости» не получат последнее next-оповещение. Они получат только complete.
Специально для этого оповещения должны храниться в состоянии Subject`а. Именно это делает класс AsyncSubject и именно для этого мы используем AsyncSubject в подобной ситуации.
Что касательно других классов Subject`ов?
Существует двое других вариантов Subject`ов: BehaviorSubject и ReplaySubject.
Чтобы понимать BehaviorSubject лучше, давайте рассмотрим пример:
Здесь родительский компонент соединяется с awesome-component при помощи Subject и применяет оператор startWith. Этот оператор обеспечивает надежный прием значения “awesome” вместе со значениями, сгенерированными awesome-component – в случае, конечно, если они таки были сгенерированы.
Подобно тому, как AsyncSubject используется вместо обычного Subject`а и оператора last, BehaviorSubject может заменить собой оператора startWith и Subject`а – так как его конструктор принимает значение, которое было бы в противном случае направлено к startWith.
В случае с использованием BehaviorSubject все подписчики получат начальное значение. Это возможно потому, что BehaviorSubject хранит значение переменной в своем состоянии.
По той причине, что концепция «переигрывания» уже полученных оповещений внедрена в мульти-подписку, аналогии с единым подписчиком для ReplaySubject просто не существует. Так же, как и BehaviorSubject, переменные хранятся в состоянии Subject`а.
Итак, как мы используем эти Subject`ы?
Мы увидели, какие бывают Subject`ы и для чего они используются. Но как они должны быть использованы? Что ж, как бы это ни было парадоксально, но класс Subject – это тот класс, который вам, вероятно, никогда не придётся использовать.
Subject работает прекрасно при связывании «наблюдателя» с «наблюдаемостью». Но для ситуаций с мультикастингом существуют альтернативы.
Бесплатные вебинары по схожей тематике:
RxJS содержит операторы мультикастинга, которые используют различные Subject – классы, причем точно так же, как я могу использовать генераторы «наблюдаемостей» RxJS (fromEvent) над вызовами Observable.create. Но для ситуаций с мультикастингом я все же предпочитаю использовать следующие операторы:
- Publish или share могут быть использованы вместо Subject;
- publishBehavior может быть использован вместо BehaviorSubject;
- publishLast может быть использован вместо AsyncSubject;
- publicReplay или shareReplay могут быть использованы вместо ReplaySubject.
Автор перевода: Евгений Лукашук
Статьи по схожей тематике