프로필사진
BehaviorSubject vs ReplaySubject

2021. 1. 16. 18:00🔴 FE/RxJS

300x250

BehaviorSubject와 ReplaySubject의 차이점을 예제를 통해 알아보려고 한다.


Official Documentation

- BehaviorSubject

A variant of Subject that requires an initial value and emits its current value whenever it is subscribed to.
initial 값을 지정해야 하고, 구독이 됐을 때 현재의 값을 emit 하는 Subject.

- ReplaySubject

A variant of Subject that “replays” or emits old values to new subscribers. It buffers a set number of values and will emit those values immediately to any new subscribers in addition to emitting new values to existing subscribers.
새로운 구독이 일어났을 때 이전의 값들을 emit하는, 즉 '리플레이'하는 Subject.
정해진 수의 값들을 가지고 있다가 새로운 구독자들이 생겼을 때 바로 그 값들을 새로운 구독자와 기존 구독자에게 emit 한다.


medium.com/@luukgruijs/understanding-rxjs-behaviorsubject-replaysubject-and-asyncsubject-8cc061f1cfc0

 

Understanding rxjs BehaviorSubject, ReplaySubject and AsyncSubject

And explore their different characteristics

medium.com

( 해당 글 일부분을 번역하였습니다. )

BehaviorSubject

현재의 값을 저장하고 있는 것이 특징인 Subject, 따라서 우리는 항상 가장 최근에 emit 된 값을 가져올 수 있다.

가장 최신의 값을 가져오는 방법은 두 가지가 있다 :
- BehaviorSubject의 .value 속성을 참고하기
- BehaviorSubject을 구독하기

만약 BehaviorSubject을 구독한다면 BehaviorSubject은 구독자에게 바로 최신 값을 emit 해준다.
BehaviorSubject가 값을 저장하고 나고 한참 뒤에 구독을 해도 최신 값을 받을 수 있다.

import * as Rx from "rxjs";

const subject = new Rx.BehaviorSubject();

// subscriber 1
subject.subscribe((data) => {
    console.log('Subscriber A:', data);
});

subject.next(Math.random());
subject.next(Math.random());

// subscriber 2
subject.subscribe((data) => {
    console.log('Subscriber B:', data);
});

subject.next(Math.random());

console.log(subject.value)

// output
// Subscriber A: 0.24957144215097515
// Subscriber A: 0.8751123892486292
// Subscriber B: 0.8751123892486292
// Subscriber A: 0.1901322109907977
// Subscriber B: 0.1901322109907977
// 0.1901322109907977

1. BehaviorSubject으로 Subject을 생성한 뒤 이를 'Subscriber A'로 구독한다. Subject가 값을 emit 하고, Subscriber A가 랜덤 숫자를 로그에 찍는다.
// Subscriber A: 0.24957144215097515

2. Subject가 다음 값을 emit하고, Subscriber A가 해당 값을 또 찍는다.
// Subscriber A: 0.8751123892486292

3. Subscriber B가 Subject를 구독하는데, BehaviorSubject이기 때문에 새로운 구독자는 마지막으로 저장된 값을 자동으로 받게 되고 이를 로그에 찍는다.
// Subscriber B: 0.8751123892486292

4. Subject가 새로운 값을 emit 한다. Subscriber A와 B 모두 새로운 값을 받고 로그에 찍는다.
// Subscriber A: 0.1901322109907977
// Subscriber B: 0.1901322109907977

5. .value 속성을 참고하여 최신 값을 로그에 찍었다. 구독할 필요 없이 최신의 값을 가져올 수 있다.
// 0.1901322109907977

BehaviorSubject를 start value와 함께 생성할 수도 있다.
초기 값을 지니고 있는 Observables을 생성하는 것은 좀 어려울 수 있으나, 아래의 예시처럼 초기 값을 전달해주는 BehaviorSubject으로 쉽게 만들 수 있다.

import * as Rx from "rxjs";

const subject = new Rx.BehaviorSubject(Math.random());

// subscriber 1
subject.subscribe((data) => {
    console.log('Subscriber A:', data);
});

// output
// Subscriber A: 0.24957144215097515

/

ReplaySubject

BehaviorSubject와 달리, ReplaySubject은 새로운 구독자들에게 기존의 값들을 보내준다.
ReplaySubject는 observable 실행의 일부분을 기록할 수 있어서 여러 개의 기존 값들을 저장하여 새로운 구독자들에게 '리플레이'해줄 수 있다.

ReplaySubject를 생성할 때 몇 개의 값을 얼마나 저장할 것인지 지정할 수 있다.
(예를 들면 : 새로운 구독이 일어나기 1초 전의 5개의 값을 저장)

import * as Rx from "rxjs";

const subject = new Rx.ReplaySubject(2);

// subscriber 1
subject.subscribe((data) => {
    console.log('Subscriber A:', data);
});

subject.next(Math.random())
subject.next(Math.random())
subject.next(Math.random())

// subscriber 2
subject.subscribe((data) => {
    console.log('Subscriber B:', data);
});

subject.next(Math.random());

// Subscriber A: 0.3541746356538569
// Subscriber A: 0.12137498878080955
// Subscriber A: 0.531935186034298
// Subscriber B: 0.12137498878080955
// Subscriber B: 0.531935186034298
// Subscriber A: 0.6664809293975393
// Subscriber B: 0.6664809293975393

1. ReplaySubject를 생성하고 2개의 값만 저장할 것이라고 지정한다.

2. Subscriber A로 구독한다.

3. subject를 통해 3개의 새로운 값을 실행하였고, Subscriber A는 3개 모두 로그에 찍는다.
// Subscriber A: 0.3541746356538569
// Subscriber A: 0.12137498878080955
// Subscriber A: 0.531935186034298

4. Subscriber B로 구독은 시작한다. ReplaySubject에 2개의 값을 저장한다고 지정했기 때문에 ReplaySubject은 Subscriber B에게 바로 마지막 2개의 값을 emit 해주고 이는 로그에 찍힌다.
// Subscriber B: 0.12137498878080955
// Subscriber B: 0.531935186034298

5. subject이 다른 값을 또 emit 한다. 이번에는 Subscriber A, B 모두 해당 값을 로그에 찍는다.
// Subscriber A: 0.6664809293975393
// Subscriber B: 0.6664809293975393


ReplaySubject(1)으로 생성하면 처음에 값이 emit 되고 나서 항상 1개의 최신 값을 저장하고 있기 때문에
초기값을 설정할 수 있는 BehaviorSubject와 동일하게 사용할 수 있어 보인다.

하지만 경우에 따라 다르게 기능을 하니 이를 살펴보자.

medium.com/javascript-everyday/behaviorsubject-vs-replaysubject-1-beware-of-edge-cases-b361153d9ccf

 

BehaviorSubject vs ReplaySubject(1) -beware of edge cases

According to the official documentation:

medium.com

( 해당 글 일부분을 번역하였습니다. )

BehaviorSubject vs ReplaySubject(1)

1. 막 생성했을 경우 - BehaviorSubject 승!

만약 BehaviorSubject를 사용한다면, 초기값을 설정해줘야 하는데 이는 결과 스트림에다가 startWith를 사용하는 것과 비슷하다. 반대로, ReplaySubject는 초기값을 전달할 수 없다.

import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';

function observerFactory(name: string) {
  return {
    next: color => console.log(`#${name}`, color)
  };
}

const behaviorSubject = new BehaviorSubject('Angular 7');
const replaySubject = new ReplaySubject<string>(1);

const behaviorObserver = observerFactory('behavior');
const replayObserver = observerFactory('replay');
const lateReplayObserver = observerFactory('late replay');

behaviorSubject.subscribe(behaviorObserver);
replaySubject.subscribe(replayObserver);

setTimeout(() => {
  behaviorSubject.next('Angular 8');
  replaySubject.next('Angular 8');
}, 1000);

setTimeout(() => {
  replaySubject.subscribe(lateReplayObserver);
}, 1500);

//console output

//---at moment of subscription---
//#behavior Angular 7

//---after 1000ms---
//#behavior Angular 8
//#replay Angular 8

//--after 1500ms---
//#late replay Angular 8

마지막 값(Angular 8)이 'late observer'에서 리플레이되었다.
그러므로 subject에 첫 번째 값을 넣은 뒤에 BehaviorSubject은 ReplaySubject(1)과 동일하게 실행된다.

2. Complete 되었을 경우 - ReplaySubject(1) 승!

만약 이미 complete 된 subject을 구독한다면 BehaviorSubject은 더이상 마지막 값을 전달받을 수 없다. 하지만 ReplaySubject(1)는 complete되기 전의 마지막 값을 전달한다.

import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';

function observerFactory(name: string) {
  return {
    next: color => console.log(`#${name}`, color)
  };
}

const behaviorSubject = new BehaviorSubject('Angular 7');
const replaySubject = new ReplaySubject<string>(1);

const behaviorObserver = observerFactory('behavior');
const replayObserver = observerFactory('replay');
const lateBehaviorObserver = observerFactory('late behavior');
const lateReplayObserver = observerFactory('late replay');

behaviorSubject.subscribe(behaviorObserver);
replaySubject.subscribe(replayObserver);

setTimeout(() => {
  behaviorSubject.next('Angular 8');
  replaySubject.next('Angular 8');
}, 1000);

setTimeout(() => {
  behaviorSubject.complete();
  replaySubject.complete();

  behaviorSubject.subscribe(lateBehaviorObserver);
  replaySubject.subscribe(lateReplayObserver);
}, 1500);

//console output

//---at moment of subscription---
//#behavior Angular 7

//---after 1000ms---
//#behavior Angular 8
//#replay Angular 8

//--after 1500ms---
//#late replay Angular 8

결론

- 구독 중에 subject에 값을 넣지 않아도 초기값을 전달하고 싶다면 BehaviorSubject을 사용
- subject가 complete된 후에도 마지막 값을 observer에게 다시 전달해줘야 한다면 ReplaySubject(1)을 사용

300x250