Наблюдение за температурой: введение в холодные и горячие Observable в rxjs

4d075f35af151f72de30f182c6acd02d.png

По мере изучения RxJS разработчик рано или поздно сталкивается с такими понятиями, как cold и hot observable. А на технических собеседованиях в команды, которые используют RxJS, можно услышать вопросы по этой теме.

Например, чем горячий поток отличается от холодного? Можно ли холодный поток превратить в горячий и наоборот? И если да, то как это сделать?

В статье попробуем разобраться в теме и найти ответы.

Аналогия из реальной жизни

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

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

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

Неплохая аналогия, расскажу, что она значит в реальной жизни.

Горячий и холодный observable в коде

В документации сказано:  

Cold Observable — это поток, источник данных которого создается внутри конструктора Observable.

Hot Observable — это поток, источник данных которого создается снаружи конструктора Observable.

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

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

Следуя определению, создадим холодный observable и подпишемся на него три раза в разные промежутки времени.

function coldDate() {
 return new Observable((subscriber) => {
   const value = getCurrentDate();
   subscriber.next(value);
 });
}

const observable = coldDate();

const s1 = observable.subscribe(console.log);

setTimeout(() => {
 const s2 = observable.subscribe(console.log);
}, 2000);

setTimeout(() => {
 const s3 = observable.subscribe(console.log);
}, 4000);

Запустив код, мы увидим, что у каждого подписчика значение даты и времени свое, которое генерируется в момент подписки на observable.

Теперь посмотрим, какое поведение будет, когда observable будет горячим.

function hotDate() {
 const value = getCurrentDate();
 return new Observable((subscriber) => {
   subscriber.next(value);
 });
}

const observable = hotDate();

const s1 = observable.subscribe(console.log);

setTimeout(() => {
 const s2 = observable.subscribe(console.log);
}, 2000);

setTimeout(() => {
 const s3 = observable.subscribe(console.log);
}, 4000);

В этом случае значение даты и времени у всех то, что было сгенерировано еще при создании observable. Стоит уточнить, что источник данных для горячего observable может находиться и за пределами функции фабрики.

От того, где инициализируется источник данных, будет зависеть, какие значения станут получать подписчики. Но почему так происходит?

Внутренности Observable

Чтобы понять, почему функция subscribe создает или не создает источник данных, вспомним, как устроен Observable.

class Observable {
 constructor(private onSubscribe: (observer: Observer) => Subscription) {}

 subscribe(observer: Observer) {
   this.onSubscribe(observer);
 }
}

При вызове функции subscribe у инстанса класса Observable мы вызываем функцию onSubscribe, которую передавали в конструктор. Значит, в cold observable источник данных будет создаваться каждый раз заново, потому что он создается в этом колбэке, в отличие от hot observable.

Основные различия:

Свойства потока Hot observable

Свойства потока Cold observable

Источник данных создается снаружи конструктора observable, поэтому данные в нем могут меняться, даже если нет подписчиков

Поток делится значениями, пришедшими ему из источника данных, со всеми своими подписчиками

Источник данных создается внутри конструктора Observable

Источник данных для каждого подписчика свой, а значит, и значения тоже свои

Как подогреть или остудить observable

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

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

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

const observable = coldDate();

const subject = new Subject();

observable.subscribe(subject);

const s1 = subject.subscribe(console.log)

Тогда все наши подписчики будут получать одни и те же значения. Этот же трюк проделывают и операторы мультикастинга — такие как share или shareReply:

const hot$ = cold$.pipe(shareReply(1))

Более подробно тему мультикастинга разберем в следующей статье.

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

Заключение

Надеюсь, статья помогла разобраться с горячими и холодными потоками и понять, как они работают. Спасибо за внимание! Если у вас есть вопросы, интересные примеры или кейсы по работе с потоками — буду рад обсудить в комментариях.

© Habrahabr.ru