RxJS довольно популярен в мире JavaScript, имея около 17 миллионов загрузок в минуту. Если вы разработчик Angular, вы наверняка не пропустили RxJS Observables, но вы можете быть менее знакомы с Subjects. Несмотря на то, что они реже, чем простые Observables, они чрезвычайно полезны. Понимание их поможет вам написать лучший и более чистый реактивный код.

Наблюдаемые

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

Наблюдаемые - это ленивые Push-коллекции нескольких значений.

Например, мы можем использовать Observables для выдачи чисел от 0 до 59 каждую секунду:

Наблюдатель, подписавшийся на этот Observable, будет получать значение каждую секунду:

Вам нужно подписаться на Observable, чтобы он начал подсчет, точно так же, как вам нужно вызвать функцию для ее вычисления. Также, как и в случае с функциями, второй «вызов» инициирует новое независимое выполнение. Если вы снова подпишетесь на этот Observable через две секунды, вы увидите оба «счетчика» на своей консоли, причем второй будет иметь двухсекундную задержку.

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

Предметы

Subject похож на Observable, но может выполнять многоадресную рассылку для многих Observers.

Субъекты - это наблюдаемые. Мы можем создать один и тот же счетчик, выдающий числа от 0 до 59 каждую секунду, используя Subject:

Вероятно, вы заметите основное отличие в нашем предыдущем примере. При объявлении Observable мы дали функцию в качестве параметра, сообщая Observable, что отправлять подписчику. Это было нормально, потому что каждый новый подписчик начинал новое выполнение. В этом случае, с другой стороны, у нас есть только одно исполнение, и новые подписчики просто начинают его «слушать». Мы просто создаем новый объект с помощью new Subject ().

Мы также можем подписаться на нашу тему, потому что Тема является наблюдаемым. Затем мы вызываем next непосредственно для нашего субъекта, потому что субъект является наблюдателем.

Любой новый подписчик будет добавлен в список подписчиков, который Subject хранит внутри, и одновременно получит те же значения, что и другие подписчики. Если мы подпишемся на Subject через две секунды после первой подписки, новый подписчик пропустит первые два значения:

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

Поведение

Проблема, с которой вы можете столкнуться с Subject, заключается в том, что наблюдатель будет получать только те значения, которые были отправлены после того, как он подписался на Subject. В предыдущем примере второй эмиттер не получил значений 0, 1 и 2. Иногда вам нужно знать, какое значение было отправлено последним объектом, прежде чем вы на него подписались. Так будет, например, если вы укажете дату. Любой обозреватель, подписавшийся 1 марта, должен получить 01-03-20 по подписке, независимо от того, в какое время он подписался. В полночь каждый подписчик получает уведомление об изменении даты.

Для таких случаев вы можете использовать BehaviorSubject. BehaviorSubject сохраняет в памяти последнее переданное значение. При подписке наблюдатель немедленно получает последнее переданное значение. Если мы адаптируем предыдущий пример, это будет означать, что второй наблюдатель получит значение 2 при подписке, а затем все остальные значения, как и первый наблюдатель.

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

ReplaySubjects

ReplaySubjects очень похожи на BehaviorSubjects. Разница в том, что они запоминают не только последнее значение, а столько, сколько вы хотите. По подписке они отправляют все значения, которые они помнят, новому наблюдателю.

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

Когда второй наблюдатель подписывается на ReplaySubject, 0, 1 и 2 уже отправлены. Поскольку ReplaySubject сохранил два последних значения, второй наблюдатель немедленно получает 1 и 2.

AsyncSubjects

С AsyncSubjects ваши наблюдатели фактически ничего не получают, пока Subjects не завершится.

Используя AsyncSubject в нашем примере, мы должны ждать целую минуту, прежде чем наблюдатели что-либо получат.

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

Любой наблюдатель, подписавшийся на AsyncSubject после его завершения, получит то же значение.

В этом примере третий наблюдатель подписывается на AsyncSubject через пять секунд после его завершения. При подписке получает последнее значение: 59.

Это делает AsyncSubjects полезным для выборки и кеширования значения, такого как HTTP-ответ, который вы хотите получить только один раз, но впоследствии получить доступ из разных мест.

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