Всегда знайте, когда использовать Share и ShareReplay

Использование 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, свяжитесь с нами прямо сейчас. 🙂 🙂

Оцените статью
devanswers.ru
Добавить комментарий