최근 들어 다양한 타입스크립트 라이브러리들을 사용해보며 수많은 예시 코드들을 참조하고 실행하고 있다. 그러던 중 예시 코드에서 공통으로 사용되고 있는 RxJS 가 눈에 띄게 되었다. RxJS의 Observable이 무엇이길래 예시 코드에 작성해야 하고, 도대체 어떤 라이브러리 이길래 다른 수많은 라이브러리에서 참조하는 것인지 궁금해지기 시작했다. 그렇게 RxJS의 궁금증을 풀기 위한 글을 시작해보도록 하겠다.
RxJS란 무엇인가?
RxJS(Reactive Extensions for JavaScript)는 비동기 또는 콜백 기반 코드를 더욱 쉽게 작성할 수 있도록 Observable을 사용하는 반응형 프로그래밍용 라이브러리이다.
RxJS는 다양한 이벤트들을 Observable 이라는 타입으로 추상화하여, 각각의 이벤트들을 데이터 스트림으로 사용할 수 있도록 도와주는 라이브러리이다. 일반적으로 비동기 작업 및 데이터 스트림을 처리하는 상황에서 주로 사용되고 있다.
그렇다면, RxJS가 어떤 문제를 해결하기 위해서 개발되었을까? RxJS가 존재하지 않았던 이전 상황부터 알아야 한다. 이전에는 Rx (Reactive Extensions)라는 함수형과 반응형 프로그래밍 패턴을 결합하여 사용할 수 있는 라이브러리가 존재하였다. 하지만, Javascript 버전으로는 존재하지 않았고, 이후 Javascript를 사용하는 웹 브라우저에서 비동기적으로 동영상과 같은 데이터 스트림을 처리하는 상황이 많이 생겨났다. 이런 상황을 해결하기 위해 Rx를 Javascript로 이식한 RxJS가 탄생하게 되었다.
리액티브 프로그래밍(Reactive Programming)
리액티브 프로그래밍(Reactive Programming)은 비동기 데이터 스트림과 변경된 데이터의 전달에 중점을 둔 프로그래밍 패러다임이다. 특정 개체를 실시간으로 관찰하고 반응할 수 있는 스트림으로써 데이터와 이벤트를 모델링하는 것 또한 리액티브 프로그래밍이다.
RxJS에 대한 개념을 이야기할 때, 빠질 수 없는 것은 Rx(Reactive X)라는 이름에서도 유추할 수 있는 리액티브 프로그래밍이다. 여기서, 리액티브 프로그래밍이란 데이터 및 이벤트를 실시간으로 관찰하고 반응할 수 있도록 데이터 스트림을 비동기로 처리하여 비즈니스 로직 변화에도 유연하게 변경할 수 있도록 구성하는 프로그래밍 패러다임이다.
RxJS를 사용하는 이유
우리는 프로그래밍을 하면서 HTTP 또는 데이터베이스 통신과 같은 여러 가지의 비동기 비즈니스 로직을 구현하게 된다. 하지만, 이런 비동기 코드가 많아지게 되었을 때 많은 제어의 흐름이 얽히게 될 것이고, 그로 인해 우리는 코드의 결괏값을 예측하기 어려워지게 될 것이다.
그렇다면, 우리는 이런 상황을 어떻게 해결하기 위해선 어떻게 해야 하는가? RxJS는 이런 비동기적인 프로그래밍의 문제를 해결하기 위한 해법이 될 것이다.
Observable 이해하기
Observable에 대한 이야기를 하기에 앞서. 디자인패턴 중 옵저버 패턴(Observer Pattern)에 관해서 확인하고 넘어가면 좋을 것 같다.
옵저버 패턴(Observer Pattern)이란, 특정 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해서 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 패턴이다.
왜 옵저버 패턴에 대해 먼저 알아보았을까? 그것은 바로 RxJS의 Observable은 옵저버 패턴의 Observer와 유사하게 동작하기 때문이다. RxJS Observable에서 관찰자(Observer)는 Observable을 구독(Subscribe)하며 Observable이 제공하는 다양한 이벤트에 반응하여 동작하기 때문이다.
하나의 Observable에 대한 예시를 확인해보자.
import { Observable, Observer, Subscriber } from 'rxjs';
function subscribeEvent(subscriber: Subscriber<any>) {
subscriber.next(1); // observer에게 1을 전달한다.
subscriber.next(2); // observer에게 2를 전달한다.
subscriber.next(3); // observer에게 3을 전달한다.
setTimeout(() => { // 1000ms 이후 실행한다.
subscriber.next(4); // observer에게 4를 전달한다.
subscriber.complete(); // observer에게 완료를 전달한다.
}, 1000);
}
const observable = new Observable(subscribeEvent);
console.log('just before subscribe');
const observer: Observer<any> = {
next: (x) => console.log('got value ' + x), // next가 전달되면 console.log를 실행한다.
error: (err) => console.error('something wrong occurred: ' + err), // error가 전달되면 console.error를 실행한다.
complete: () => console.log('done'), // complete가 전달되면 console.log를 실행한다.
}
observable.subscribe(observer);
console.log('just after subscribe');
위 예시 코드에서는 가장 먼저 subscribeEvent 함수를 이용하여 Observable을 생성하게 된다. 이는 생성된 observable 인스턴스가 subscribe 메서드에 새로운 observer가 구독하게 되었을 때 실행될 이벤트를 미리 정의한 것이다.
그다음, observer 객체를 선언하여 Observable을 구독(subscribe)하게 된다. 여기서 observer 객체에는 next, error, complete 3가지의 속성들이 존재하는데, 각각의 역할은 아래와 같다.
- next: Observable 구독자에게 데이터를 전달한다.
- complete: Observable 구독자에게 모든 비즈니스 로직이 완료되었음을 전달한다. 이후 next를 수행하더라도 데이터를 전달하지 않는다.
- error: Observable 구독자에게 에러를 전달한다. 이후 next 및 complete 이벤트를 수행하더라도 이벤트가 실행되지 않는다.
그렇다면, subscribeEvent 함수를 이용해 observable 인스턴스를 생성하였으므로, 결괏값은 next가 3번 실행된 후 1초의 시간을 기다렸다가 next가 1번 실행되고 완료될 것이다. 생각한 결괏값이 맞는지 아래를 확인해보자.
just before subscribe
got value 1
got value 2
got value 3
just after subscribe
got value 4
done
Observable과 함수
Observable과 함수는 아주 유사하게 동작한다. 가장 먼저 일반 함수가 동작하는 예제를 확인해보자
function foo() {
console.log('Hello');
return 42;
}
const x = foo();
console.log(`First subscription: ${x}`);
const y = foo();
console.log(`Second subscription: ${y}`);
foo 함수가 동작하여 출력되는 결과값은 아래와 같을 것이다.
Hello
First subscription: 42
Hello
Second subscription: 42
위와 동일한 동작을 하는 Observable은 아래와 같이 사용한다.
import { Observable, Subscriber } from 'rxjs';
const subscribeEvent = (subscriber: Subscriber<any>) => {
console.log('Hello');
subscriber.next(42);
}
const foo = new Observable(subscribeEvent);
foo.subscribe((x) => {
console.log(`First subscription: ${x}`);
});
foo.subscribe((y) => {
console.log(`Second subscription: ${y}`);
});
함수는 foo();와 같이 호출하지 않으면 Hello라는 출력이 발생하지 않는다. Observable의 subscribe 메서드 또한 함수와 같이 호출하지 않으면 Hello라는 출력이 발생하지 않는다. 이처럼 Observable을 이용해 구독(subscribe)하는 것과 함수를 호출(call)하는 것은 유사하다.
자주 사용하는 RxJs 연산자(Operators)
RxJS에서는 생성 연산자, 변환 연산자, 필터링 연산자 등 다양한 연산자들을 제공한다.
RxJS에서는 Observable을 생성하는 방법은 여러 가지가 존재한다. 기본적으로 Observable 클래스를 이용해 생성하며 interval, fromEvent, from 등 다양한 방법으로 Observable을 생성할 수 있다. 이것을 생성 연산자라 부른다.
- interval: 일정 시간 간격으로 실행된 횟수를 반환하는 Observable을 생성한다.
- fromEvent: 특정 대상에게 전달되는 이벤트를 전달받아 Observable을 생성한다. document.addEventListener를 대체하여 사용할 수 있다.
- ajax: ajax로 통신을 수행하며, 결괏값을 Observable로 생성한다.
map, filte와 같은 Array.prototype의 메서드와 유사한 다양한 연산자를 변환 연산자, 필터링 연산자라 부른다.
- map: Observable Stream에서 전달받은 값을 반환한다. Array.prototype.map과 유사하게 사용한다.
- filter: Observable Stream에서 전달받은 값을 필터링한다. Array.prototype.filter와 유사하게 사용한다.
- take: Observable Stream에서 전달받은 값 중 첫 번째 값 부터 일정 개수만 가져온다. Array.prototype.slice와 유사하다.
결론
RxJS를 사용하게 되었을 때, 아래와 같은 장점을 얻을 수 있게 될 것이다.
- 비동기 작업 및 데이터 스트림을 처리할 수 있게 된다.
- 실시간으로 데이터 변경에 응답할 수 있는 반응형 코드를 작성할 수 있는 반응형 프로그래밍이 가능하게 된다.
- 명령형으로 작성하는 코드보다 가독성이 높아지게 된다.
'분석과 탐구' 카테고리의 다른 글
제로부터 시작하는 Prisma와 Nest.js (0) | 2023.07.16 |
---|---|
아키텍처 패턴 그리고 헥사고날 아키텍처 (0) | 2023.07.02 |
제로부터 시작하는 DDD를 위한 이벤트스토밍 (0) | 2023.06.18 |
Amazon API Gateway의 WebSocket API란 무엇인가? (0) | 2023.05.21 |
1일 1커밋에서 벗어나기 (0) | 2023.05.07 |