본문 바로가기

Web/HTML, CSS, Javascript

[Javascript] RxJS - Operators

https://rxjs.dev/guide/operators 번역

오역 부분이나 조언(?)은 알려주시면 감사하겠습니다.

 

RxJS는 Observable이 기본이지만, 대부분 연산자에 유용합니다. 연산자는 복잡한 비동기코드를 선언적 방식으로 구성하는데 필수적인 부분입니다.

연산자란?

연산자는 함수입니다. 2종류의 연산자가 있습니다 :

 

파이프형 연산자들은 observableInstance.pipe(operator()) 구문을 사용해 Observable로 파이프 될 수 있습니다. 이는 filter(...)mergeMap(...)을 포함합니다. 호출되면 Observable 객체를 변경하지는 않습니다. 대신, 새로운 Observable을 반환합니다. 구독 로직은 첫번째 Observable에 기반합니다.

 

파이프형 연산자는 Observable을 input처럼 취해서 다른 Observable을 반환하는 함수입니다. 이는 순수 연산입니다 : 이전 Observable은 수정되지 않습니다.

파이프형 연산자는 필수적으로 순수함수입니다. Observable을 받아 다른 Observable을 생산합니다. 반환된 Observable을 구독하는 것은 input Observable 또한 구독하는 것입니다.

 

생성 연산자들은 다른 종류의 연산자입니다. 이는 새로운 Observable을 생성하기 위해 단독 함수로써 호출될 수 있습니다. 예를 들어, of(1, 2, 3) 은 1, 2, 3을 하나씩 차례로 내보내는 Observable을 생성합니다. 생성 연산자들은 이후의 섹션에서 더 상세하게 다루겠습니다.

 

예를들어, map 이라는 연산자는 같은 이름의 배열 메소드와 유사합니다. [1, 2, 3].map(x => x * x) 의 결과는 [1, 4, 9] 인 것처럼, Observable은 아래와 같이 생성됩니다:

 

import { of } from 'rxjs';
import { map } from 'rxjs/operators';

map(x => x * x)(of(1, 2, 3)).subscribe((v) => console.log(`value: ${v}`));

// Logs:
// value: 1 
// value: 4
// value: 9

 

1, 4, 9의 결과가 나옵니다. 다른 유용한 연산자는 first 입니다:

 

import { of } from 'rxjs';
import { first } from 'rxjs/operators';

first()(of(1, 2, 3)).subscribe((v) => console.log(`value: ${v}`));

// Logs:
// value: 1

 

map은 논리적으로 즉석에서 구성되어야 합니다. 매핑 기능을 제공해야 하기 때문입니다. 대조적으로, first는 변함없는 값일 수 있습니다. 하지만 그럼에도 불구하고 즉석에서 구성되어야 합니다. 일반적으로 모든 연산자는 인수가 필요한지 여부에 관계없이 구성됩니다.

Piping

파이프형 연산자들은 함수입니다. 그래서 보통의 함수처럼 사용될 수 있습니다. op()(obs) ㅡ 하지만 실제로는, 많은 연산자들이 함께 모이는 경향이 있습니다. 그리고 읽을수 없게 되어버립니다: op4()(op3()(op2()(op1()(obs)))). 이런 이유로, Observable은 .pipe()라는 메소드가 있습니다. 이는 똑같은 일이지만 훨신 가독성이 좋게 해줍니다: 

 

obs.pipe(
  op1(),
  op2(),
  op3(),
  op3(),
)

 

op()(obs) 는 절대 쓰이지 않습니다. 심지어 하나의 연산자만 있더라도 obs.pipe(op()) 가 보편적으로 선호됩니다.

Creation Operators

생성 연산자란? 파이프형 연산자와는 구분되게, 생성 연산자들은 흔한 사전 정의된 행동이나 다른 Observable들을 조합함으로써 Observable을 생성하는데 사용되는 함수입니다.

 

생성 연산자의 전형적인 예제는 interval 함수입니다. 이는 숫자를 인자로 받아 Observable을 생성해 반환합니다:

 

import { interval } from 'rxjs';

const observable = interval(1000 /* number of milliseconds */);

 

모든 정적 생성 연산자 리스트 입니다.

Higher-order Observables (고차 Observables)

Observable은 가장 보편적으로 string, number 같은 보통의 값들을 내보냅니다. 하지만 놀랍게도 종종, Observable의 Observable을 다뤄야 할 필요가 있습니다. 소위 고차 Observable이라 합니다. 예를 들어, 파일의 URL인 string들을 내보내는 Observable이 있다고 상상해봅시다. 코드는 아래와 같을 것입니다:

 

const fileObservable = urlObservable.pipe(
   map(url => http.get(url)),
);

 

http.get()은 각각의 URL에 대해 (string 혹은 string 배열의) Observable을 반환합니다. 이제 Observable의 Observable(고차 Observable)이 생겼습니다.

 

하지만 고차 Observable로 어떻게 작업을 해야할까요? 일반적으로, 평탄화해서 사용합니다: 어떤 방법을 이용해서 고차 Observable을 하나의 보통의 Observable로 바꿔줍니다. 예시:

 

const fileObservable = urlObservable.pipe(
   map(url => http.get(url)),
   concatAll(),
);

 

concatAll() 연산자는 바깥의 Observable에서 나온 안쪽 각각의 Observable들을 구독하고, 한 Observable이 끝날때까지 도출된 값들을 복사합니다. 그리고 다음으로 넘어갑니다. 모든 값들은 그런식으로 연결됩니다. 다른 유용한 평탄화 연산자(소위 join 연산자)들은 

  • mergeAll() — 도착하는대로 안쪽의 Observable을 구독하고 값을 도출합니다.
  • switchAll() — 첫번째 안쪽 Observable을 도착하는대로 구독하고, 값을 도출합니다. 하지만 다음 안쪽 Observable이 도착하면, 이전의 것은 구독 해제하고 새로운 것을 구독합니다.
  • exhaust() — 첫번째 안쪽 Observable을 도착하는대로 구독하고 값을 도출합니다. 첫번째가 끝나기 전까지는 다른 새로운 Observable들은 무시합니다. 끝나면 다음 Observable을 기다립니다.

많은 배열 라이브러리가 map()flat() (혹은 flatten())을 flatMap() 하나로 합친것 처럼, 이에 상응하는 RxJS 평탄화 연산자들이 있습니다. concatMap()mergeMap()switchMap(), and exhaustMap().

Marble diagrams(구슬 다이어그램)

연산자들의 동작을 설명하기에 종종 문자로는 충분치 않습니다. 많은 연산자들은 시간과 연관되있습니다. 예를들어, 연산자들은 여러가지 방법으로 값 배출을 지연, 샘플링, 조절, 또는 디바운스 할 수 있습니다. 다이어그램은 종종 이것에 더 나은 방법입니다. 구슬 다이어그램은 어떻게 연산자들이 동작하는지 시각적으로 보여주고, input Observable, 연산자와 파라미터, output Observable을 포함하고 있습니다.

 

구슬 다이어그램에서, 시간 흐름은 왼->오 입니다. 이 다이어그램은 Observable 실행에서 어떻게 값들(구슬)이 배출되는지를 묘사합니다.

아래에서 구슬 다이어그램을 설명하고 있습니다.

 

 

이 문서 사이트 전체에서 구슬 다이어그램을 광범위하게 사용하여 연산자의 작동 방식을 설명합니다. 화이트 보드 나 단위 테스트 (ASCII 다이어그램)와 같은 다른 상황에서도 유용 할 수 있습니다.

Categories of operators

다른 목적의 연산자들이 있습니다. 그리고 이는 생성, 변환, 필터링, 결합, 멀티 캐스팅, 오류 처리, 유틸리티 등으로 분류 될 수 있습니다. 다음의 리스트에서는 모든 연산자가 카테고리별로 구성되어 있습니다. 

 

 

전체 개요는 참조 페이지를 참조하십시오

Creation Operators

Join Creation Operators

These are Observable creation operators that also have join functionality -- emitting values of multiple source Observables.

Transformation Operators

Filtering Operators

Join Operators

Also see the Join Creation Operators section above.

Multicasting Operators

Error Handling Operators

Utility Operators

Conditional and Boolean Operators

Mathematical and Aggregate Operators

커스텀 연산자 만들기

pipe() 함수로 새로운 연산자 만들기

만약 흔히 사용되는 연산자들의 시퀀스가 있다면, pipe() 함수를 이용해서 그 시퀀스를 하나의 연산자로 추출할 수 있습니다. 만약 시퀀스가 흔하지 않더라도, 하나의 연산자로 바꾸는 것은 가독성을 개선시킬 수 있습니다.

 

예를들어, 아래와 같이 홀수를 버리고 짝수를 2배하는 함수를 만들 수 있습니다:

 

import { pipe } from 'rxjs';
import { filter, map } from 'rxjs/operators';

function discardOddDoubleEven() {
  return pipe(
    filter(v => ! (v % 2)),
    map(v => v + v),
  );
}

 

(pipe() 함수는 Observable의 .pipe() 메소드와 유사하지만 같지는 않습니다.)

scratch로 새로운 연산자 만들기

더 복잡하지만, 기존 연산자의 조합으로 만들 수없는 연산자를 작성해야하는 경우(드물게 발생하지만...) 아래와 같이 Observable 생성자를 사용하여 처음부터 연산자를 작성할 수 있습니다:

 

import { Observable } from 'rxjs';

function delay(delayInMillis) {
  return (observable) => new Observable(observer => {
    // this function will called each time this
    // Observable is subscribed to.
    const allTimerIDs = new Set();
    const subscription = observable.subscribe({
      next(value) {
        const timerID = setTimeout(() => {
          observer.next(value);
          allTimerIDs.delete(timerID);
        }, delayInMillis);
        allTimerIDs.add(timerID);
      },
      error(err) {
        observer.error(err);
      },
      complete() {
        observer.complete();
      }
    });
    // the return value is the teardown function,
    // which will be invoked when the new
    // Observable is unsubscribed from.
    return () => {
      subscription.unsubscribe();
      allTimerIDs.forEach(timerID => {
        clearTimeout(timerID);
      });
    }
  });
}

 

Note that you must

  1. 입력 Observable을 구독할 때, 세 가지 관찰자 함수 next(), error(), complete()를 모두 구현해야 합니다.
  2. Observable이 끝났을 때 정리하는(이 경우에는 구독 취소하고 timeout 초기화) "해체(teardown)" 함수를 구현해야 합니다.
  3. Observable 생성자에 전달된 함수에서 해체함수(teardown function)를 반환합니다.

물론, 이는 한 예시일 뿐입니다. delay() 연산자는 이미 있습니다.

'Web > HTML, CSS, Javascript' 카테고리의 다른 글

[Javascript] ios 한글 조합 문제  (2) 2020.12.19
[Javascript] RxJS - Subscription  (0) 2020.07.09
[Javascript] RxJS - Observer  (0) 2020.07.06
[Javascript] RxJS - Observable  (0) 2020.07.04
[Javascript] RxJS - Overview 번역  (0) 2020.07.01