SINGLE | MULTIPLE | |
---|---|---|
Pull | Function | Iterator |
Push | Promise | Observable |
PRODUCER | CONSUMER | |
---|---|---|
Pull | Passive: produces data when requested. | Active: decides when data is requested. |
Push | Active: produces data at its own pace. | Passive: reacts to received data. |
Producer 는 데이터를 생산하는 주체이고, Consumer 는 데이터를 사용하는 주체입니다. 우리가 흔히 사용했던 함수의 관점에서 생각해보자면, 함수를 호출하는 호출자(Caller)가 Consumer 이고, 호출된 함수(Callee)가 Producer 라고 말할 수 있습니다.
우리가 일반적으로 겪어왔던 Pull 시스템에서는, Consumer 가 데이터를 언제 받는지 결정할 수 있고 Producer 는 요청을 받아 데이터를 생산하는 수동적인 역할을 할 뿐입니다. 반면 Promise 와 Observable 을 이용한 Push 시스템에서는 Producer 가 자신이 원하는 때에 데이터를 생산하고 전달할 수 있으며, Consumer 는 그 데이터에 반응할 뿐입니다.
이론적으로는 납득이 갔지만, 실제 코드가 어떨지 구체적으로 예시를 생각해 보니 헷갈리는 부분이 있었습니다. 바로 Pull 시스템 에서의 Function과 Push 시스템 에서의 Observable 에 관한 설명입니다.
먼저 Function 의 경우를 생각해 보자면, Consumer 가 데이터를 언제 받을 수 있는지 결정 할 수 있고 Function 은 요청된 데이터를 생산할 뿐이라는 점. 이 부분은 매우 당연합니다.
하지만 Push 시스템에서 Observable 은 이론이 제대로 대입되지 않았습니다. 아래의 코드 예시를 살펴봅시다.
const observable = new Observable(function subscribe(subscriber) {
subscriber.next(1);
})
observable.subscribe(x => console.log(x));
observable 에서 1 이라는 값을 notification 받아서 콘솔에 1을 출력하는 코드 입니다. 저는 이 부분에서,
라고 생각했었습니다.
그러나 이는 곧,
1. Producer 와 Consumer 의 주체가 누구인지 명확히 함과 동시에
2. subscribe() 메서드의 역할이 무엇인지 API Documentation 에서 다시한번 읽음으로써
해결되었습니다.
아까의 예시를 다시 가져와보겠습니다.
const observable = new Observable(function subscribe(subscriber) {
subscriber.next(1);
})
observable.subscribe(x => console.log(x));
이 예시를 보면, 제가 일부러 Observable 생성자에 전달되는 콜백에 subscribe
라는 이름을 붙여서 전달한 것을 볼 수 있습니다. 저 subscribe
함수가 데이터를 생산하는 Producer 입니다. 그리고 observable.subscribe(x => console.log(x))
에서 전달되는 x => console.log(x)
콜백함수가 데이터를 받아쓰는 Consumer 입니다.
제가 아까 Observable 함수에 전달되는 subscribe
콜백함수가 Producer 라고 했습니다. observable.subscribe()
에서도 subscribe
라는 메서드를 호출합니다. 네, 이것은 같은 것입니다. 공식문서 에서도 Practical purpose 에서 는 두개가 같다고 봐도 무방하다고 설명되어 있습니다.
즉, observable.subscribe()
를 한다는 것은 Observable 에 전달된 콜백함수를 실행시킨다는 의미와 일맥상통하며, Producer 가 직접 실행함으로써 Consumer 에게 자신이 생산한 데이터를 전달한다는 자연스러운 스토리가 형성됩니다.
https://rxjs.dev/api/index/class/Observable 문서에서도,
subscribe()
메서드는 Observable 의 실행을 invoke 한다고 설명돼 있습니다.
Observable Push 시스템에서 Producer 와 Consumer 가 누구인지 정확히 알게되어 궁금증이 해결되었습니다. 하지만 'lazy computation' 이라는 부분이 무언가 수동적인 느낌을 주는듯한 느낌이 아직 가시지 않았습니다.
저는 그래서, lazy = 수동
이라는 명제는 성급한 일반화의 오류라는 것을 다시 한번 상기시키려고 합니다. lazy 하지만 분명히 능동적이고 주체적일 수가 있으니까요.
마지막으로 예제를 하나 작성하여 여운을 남기고 마무리 하겠습니다.
function passiveProducer() {
setTimeout(() => {
return 'seop'; // X. Already in Task Queue
}, 5000);
const producedValueJustPassively = 'seop';
return producedValueJustPassively;
}
const value = passiveProducer();
// -------------------------------------------------------------
const lazyButActiveObservable = new Observable((subscriber) => {
setTimeout(() => {
subscriber.next('seop');
}, 5000);
})
lazyButActiveObservable.subscribe(x => console.log(`${x} value is absolutely emitted by Producer's own pace`))
P.S. 이벤트루프 에서까지 책임을 다 하는 Observable..