RxJS 빠르게 배우기 02 - Observables

RxJS 빠르게 배우기 02 - Observables

지난 글에서 RxJS로 코드를 짰을 때 어떤 느낌인지 살짝 확인 했다. 이제는 핵심적인 개념들과 실제로 어떻게 구현 되어 있는지 등을 하나씩 확인해보자.

옵저버 패턴과 옵저버블 (Observable)

Rx의 개념은 옵저버 패턴을 확장한 것이라고 알려져잇다. 그렇다면 옵저버 패턴은 뭘까? 쉽게 설명하자면 관찰의 역할을 하는 옵저버 객체들을 서브젝트라는 객체에 등록한 다음, 서브젝트 객체의 상태 변경이 일어나면 notify()를 호출해 알리게 되는 구조이다.


위키 문서 이미지

자바스크립트에서는 가장 대표적으로 EventListener를 예시로 들 수 있다.

옵저버블 타입은 ReactiveX에서 기존 옵저버 패턴에서 데이터가 없다는 것을 알리는 complete 메서드, 에러가 발생했음을 알리는 error 메서드가 추가된 것이라고 한다. RxJS는 옵저버 패턴을 적용한 Observable을 중심으로 동작한다. Observable 객체는 객체 안에서 여러 값이나 이벤트를 취급하고, 옵저버의 함수를 호출해 필요한 값이나 이벤트를 보내는 역할을 한다.


Observable이 사용되는 예시를 간단하게 확인해보자.

아래 코드는 subscribe 될 때 1, 2, 3 값을 즉시 (동기적으로) 밀어넣는 Observable이다. 그리고 값 4는 1초 뒤에 subscribe 호출로 값이 들어가고, 그 다음 완료된다.

1
2
3
4
5
6
7
8
9
10
11
12
import { Observable } from "rxjs";

const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);

setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});

Observable을 호출하고 값들을 보기 위해서는 subscribe를 해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Observable } from "rxjs";

const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});

console.log("Just before subscribe");
observable.subscribe({
next(x) {
console.log(`got value ${x}`);
},
error(err) {
console.error(`something wrong occurred: ${err}`);
},
complete() {
console.log("done");
},
});

console.log("Just after subscribe");

위 코드는 콘솔에 아래와 같은 결과를 보여준다.

1
2
3
4
5
6
7
Just before subscribe
got value 1
got value 2
got value 3
Just after subscribe
got value 4
done

Pull vs Push

Pull, Push라고 하는 것은 데이터를 만들어내는 쪽 (Producer)과 데이터를 소비하는 쪽 (Consumer) 사이에 어떻게 커뮤니케이션 하는가를 설명해주는 두 프로토콜이다. 먼저 Pull 프로토콜을 사용하는 경우에는 Consumer가 데이터를 언제 받을지를 결정하는 시스템이다. Producer는 데이터가 언제 Comsumer에게 전달될지를 알 수 없는 상태로 있는다.

모든 자바스크립트 함수는 기본적으로 Pull 시스템을 따르고 있다. 함수는 Producer라고 볼 수 있고, 해당 함수를 호출하는 코드는 호출한 결과로 하나의 리턴 값을 받는 “pulling“을 한 것이라고 볼 수 있다.

ES2015는 generator 함수들과 iterators를 소개하고 있다. 이것들은 다른 타입의 Pull 시스템을 따르고 있다. iterator.next()를 호출하고 있는 코드는 Consumer라고 할 수 있고, 그 Consumer는 다양한 값들을 이터레이터(Producer)로부터 받을 수 있게 된다.

Pull 방식이 익숙한 방식임을 알게 되었고, 그렇다면 Push는 그 반대겠지?

Push 시스템에서는 Producer가 Consumer에게 언제 데이터를 보내줘야 할지를 결정하게 된다. 반대로 Consumer는 데이터를 언제 받게 되는지를 알지 못하는 상태로 있는다.

Promise는 가장 일반적인 Push 시스템이라고 볼 수 있다. 자바스크립트를 알고 있다면 무슨 느낌인지 알 수 있기 때문에 디테일한 설명은 생략한다.


Observablefunction의 차이는 뭘까? Observable은 다수의 값을 시간 흐름에 따라서 리턴할 수 있다는 것이다. 쉽게 말하자면 함수는 아래와 같은 동작을 지원하지 않는다.

1
2
3
4
function foo() {
return 42;
return 100; // 이 값은 절대 리턴되지 않는다.
}

반면 Observable는 아래와 같은 것이 가능하다.

1
2
3
4
5
6
7
8
9
import { Observable } from "rxjs";

const foo = new Observable((subscriber) => {
subscriber.next(42);
subscriber.next(100);
subscriber.next(200);
});

foo.subscribe(console.log);

아래와 같이 나타난다.

1
2
3
42
100
200

subscribe는 next 값이 소진될 때까지 map이 도는 것과 유사하게 동작하는 것 같다. 이 부분은 그냥 여러 값을 리턴하는 것처럼 동작할 수 있군 하면서 넘어가면 될 것 같다.

정리해보자면, 여러 값을 내보낼 수 있는가? 또는 하나의 값만 내보낼 수 있는가?의 기준과 Pull 방식을 따르는가? Push 방식을 따르는가?의 기준으로 설명할 수 있다. 해당 기준으로 나누면 아래와 같다고 볼 수 있다.

Push Pull
단일 Promise Function
복수 Observable Iterable

옵저버블에 대한 자세한 내용

Obeservable이 맡는 관심사는 다음과 같다.

  • Observable 생성
  • Obeservable 구독
  • Obeservable 실행
  • Obeservable 처리 (구독 취소)

생성

Observable 생성자는 subscribe 함수 하나를 인자로 받는다.

아래 예시는 hi 문자열을 매 초마다 subscriber에게 전달하는 코드이다.

1
2
3
4
5
6
7
import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {
const id = setInterval(() => {
subscriber.next('hi')
}, 1000);
});

일반적으로는 new Observable을 사용해 만들 수 있지만, of, from, interval 등 생성 함수들을 이용해 보통 만들어진다.

구독과 실행

구독은 데이터를 전달할 콜백을 제공해 함수를 호출하는 것이라고 볼 수 있고, 실행은 Observable에서 발행하는 값을 사용하는 것 이라고 설명할 수 있다. 구독은 subscribe 함수를 이용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const observable = new Observable(function subscribe(subscriber) {

try {
subscriber.next(1);
subscriber.next(2); // 실행되는 값들

subscriber.complete();
subscriber.next(3) // 전달되지 않는다.
} catch (e) {
subscriber.error(e);
}
})

observable.subscribe(console.log); // 구독

new Observable(function subscribe(subscriber) { ... })는 구독이 발생하는 시점부터 실행되는 내용이다. Observable의 실행에서 전달할 수 있는 값의 타입은 세 가지이다.

  • Next 알림: 특정 값을 전달해준다.
  • Error 알림: 자바스크립트의 에러 또는 예외를 전달한다.
  • Complete 알림: 값을 보내지 않는다.

Next 타입은 핵심적인 데이터라고 볼 수 있고, 나머지 두 가지는 실행 중 한 번만 발생하게 되는 것들이다. Next는 무한으로 보낼 수 있지만, 나머지 두 개가 발생하고 나서는 값을 전달할 수 없게 된다.

subscribe 메서드를 통해서 next() 메서드로 전달받는 값을 처리하는 콜백을 넣을 수 있다. complete 메서드가 실행되면 구독은 끊어지고, 이후 값이 전달되지 않게 된다.

구독 취소

구독 취소는 Observable의 구독을 해제하는 것이다. subscribe 메서드를 호출하면 리턴 값으로 Subscription 객체를 리턴한다.

1
const subscription = observable.subscribe(console.log);

이 객체는 진행중인 실행을 나타내고, 최소한의 실행 취소를 위한 API를 가지고 있다.

1
2
3
4
5
6
import { from } from 'rxjs';

const observable = from([10, 20, 30]);
const subscription = observable.subscribe(console.log);

subscription.unsubscribe();

만약 create() - (아마 과거에 Observable.create() 함수를 말하는 것 같다. 현재 문서에서는 new Observable(function subscribe(subscriber) {...})) 형태로 모두 바뀌었다. - 블로그 주인)를 사용한 경우에는 커스텀한 unsubscribe 함수를 리턴해줘야 한다.

1
2
3
4
5
6
7
8
9
10
const observable = new Observable(function subscribe(subscriber) {
const intervalId = setInterval(() => {
subscriber.next('Hi');
}, 1000)

// 구독 해제 하는 방법을 함수형태로 리턴해준다.
return function unsubscribe() {
clearInterval(intervalId);
}
})

Reference

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×