Использование share
и shareReplay
довольно запутанно. То, как работают share
и shareReplay
, не всегда очевидно и может привести к неожиданному поведению в вашем приложении.
К счастью, вы нашли эту статью и после прочтения поймете разницу между share
и shareReplay
.
поделиться
Оператор share
осуществляет многоадресную рассылку значений, испускаемых источником Observable, для подписчиков.
Многоадресная рассылка означает, что данные отправляются в несколько пунктов назначения.
Таким образом, share
позволяет избежать многократного выполнения исходной Наблюдаемой при наличии нескольких подписок. share
особенно полезен, если вам нужно предотвратить повторные вызовы API или дорогостоящие операции, выполняемые Observables.
В слегка измененном коде из официальной документации, приведенном ниже, имеется разделяемый источник Observable, который испускает случайные числа с интервалом в 1 секунду, вплоть до двух выбросов. Вы также можете запустить пример на StackBlitz.
import { interval, tap, map, take, share } from 'rxjs';
const source$ = interval(1000).pipe(
tap((x) => console.log('Processing: ', x)),
map(() => Math.round(Math.random() * 100)),
take(2),
// if you remove share, you will see that
// each subscription will have its own execution of the source observable
share()
);
source$.subscribe((x) => console.log('subscription 1: ', x));
source$.subscribe((x) => console.log('subscription 2: ', x));
setTimeout(
// this subscription arrives late to the party. What will happen?
() => source$.subscribe((x) => console.log('subscription 3: ', x)),
1500
);
/* Example Run
### share operator logs:
--- 1 second
Processing: 0
subscription 1: 33
subscription 2: 33
--- 2 seconds
Processing: 1
subscription 1: 12
subscription 2: 12
subscription 3: 12
### without share operator logs:
--- 1 second
Processing: 0
subscription 1: 55
Processing: 0
subscription 2: 65
--- 2 seconds
Processing: 1
subscription 1: 64
Processing: 1
subscription 2: 2
--- 2.5 seconds
Processing: 0
subscription 3: 42
--- 3.5 seconds
Processing: 1
subscription 3: 95
*/
поделиться ‘s Inner Observable: Объект
Когда вы подписываетесь на общую Observable, вы фактически подписываетесь на Subject, открываемый оператором share
. Оператор share
также управляет внутренней подпиской на исходную Наблюдаемую. Внутренний Subject
является причиной того, что несколько подписчиков получают одно и то же общее значение, поскольку они получают значения от Subject
, раскрытого оператором share
. См. предыдущий пример на StackBlitz.
RefCount оператора share
share
ведет подсчет подписчиков. Как только количество подписчиков достигнет 0
, share
отпишется от исходной Observable и сбросит свою внутреннюю Observable (Subject
). Следующий (поздний) подписчик вызовет новую подписку на исходную Observable, или, другими словами, новое выполнение исходной Observable. Вот пример такого поведения, также доступный на StackBlitz.
import { defer, delay, of, share, shareReplay, tap } from 'rxjs';
const source$ = defer(() => of(Math.round(Math.random() * 100))).pipe(
tap((x) => console.log('Processing: ', x)),
delay(1000),
// shareReplay({ bufferSize: 1, refCount: true }),
share()
);
source$.subscribe((x) => console.log('subscription 1: ', x));
source$.subscribe((x) => console.log('subscription 2: ', x));
setTimeout(
() => source$.subscribe((x) => console.log('subscription 3: ', x)),
3500
);
shareReplay
В некоторых случаях вам действительно нужен share
, который может вести себя так же, как BehaviorSubject. Например: если холодная Observable имеет оператор share
, как в примере кода выше, поздний подписчик на нее никогда не получит значения, выданные до подписки, потому что он подписался после того, как оператор share
достиг refCount
0, что означает, что оператор share
отписался от исходной Observable и сбросил свой внутренний Subject. Таким образом, опоздавший подписчик подпишется на новый внутренний субъект, который запустит новое выполнение исходной Observable, что в данном случае означает второй вызов API: прямо противоположное тому, что вам действительно было нужно.
Вот почему существует shareReplay
: он и разделяет исходную Observable, и воспроизводит последние выбросы для поздних подписчиков.
Кроме того, по умолчанию он не ведет подсчет подписчиков, но вы можете использовать опцию refCount со значением true
, чтобы включить это поведение.
Внутренняя наблюдаемая shareReplay: ReplaySubject
В отличие от share
, shareReplay
раскрывает подписчикам ReplaySubject
. ReplaySubject(1)
очень похож на BehaviorSubject
.
ShareReplay’s RefCount
Поскольку shareReplay
по умолчанию не отслеживает счетчик подписчиков, он не может отписаться от исходной Observable. Никогда. Если только вы не используете опцию refCount
.
Чтобы использовать shareReplay
и при этом избавиться от проблем с утечкой памяти, вы можете использовать опции bufferSize
и refCount
: shareReplay({ bufferSize: 1, refCount: true })
.
shareReplay
никогда не сбрасывает свой внутренний ReplaySubject
, когда refCount
достигает 0, но отписывается от исходной Observable. Опоздавшие подписчики не будут инициировать новое выполнение исходной Observable и получат до N (bufferSize)
выбросов. Поиграйте с предыдущим примером на StackBlitz, чтобы увидеть разницу.
Используйте с осторожностью
В Angular есть некоторые проблемы при использовании share
и shareReplay
. Наблюдаемые, подписанные в шаблоне с помощью трубы async
, могут достичь refCount 0
при автоматической отписке с помощью трубы async
внутри *ngIf
, что вызовет новое выполнение исходной наблюдаемой.
Вы можете чувствовать себя богом времени и кэша, используя share
и shareReplay
, но вы должны знать, что с большой силой приходит большая ответственность. Если вам нужен партнер, который поможет вам справиться с высокой сложностью share
, shareReplay
и лучших практик RxJS, свяжитесь с нами прямо сейчас. 🙂 🙂