본문 바로가기
FrontEnd/Angular

[Angular] Using observables...

by oncerun 2022. 2. 14.
반응형

하이브리드 앱 개발 중 옵저버블을 반환하는 여러 함수를 사용하는데, 도저히 옵저버블의 개념이 와닿지 않는다. 

옵저버블은 ES7 릴리스에 포함될 비동기 데이터를 관리하기 위한 새로운 표준이다.

다만 Angular에서는 옵저버블을 정말 광범위하게 사용하기 때문에 앵귤러에서 어떻게 사용하는지 공식문서를 참고하여 조금씩 이해하기 위해 노력해보자..

 

 

 

 

옵저버블의 시작에서 관찰자 패턴을 설명한다. 

" subject라고 불리는 객체가 observers라고 불리는 종속 항목의 리스트를 관리하고 상태 변경을 자동으로 알리는 디자인 패턴입니다. "

그럼 관찰자 패턴은 subject의 객체를 구독하는 observer들에게 subject의 상태 변경을 자동으로 알리고, subject는 자신을 구독하는 observer들의 리스트를 관리한다.라고 정리해본다.

 

Observables are declarative이라고 한다. 선언적이라는 말은 객체지향 프로그래밍의 방식을 따른다는 것인가? 

추가 설명에는 다음과 같다. 이것은 값을 옵서버들에게 전달하기 위해 선언적으로 함수를 정의하지만 소비자(여기서는 옵서버인 것 같다.)가 subscribes 하기 전까지는 함수가 실행되지는 않습니다.

 

일단 여기까지 생각되는 interface를 만들어보면서 이해해보자.

interface Observables{

list : Observer[];
subscribe();
	
}

 

 

기본 용어

 

As a publisher, you create an Observable instance that defines a subscriber function. 

 - publisher는 subscriber function를 정의하는 Observable 인스턴스를 생성할 수 있게 합니다.

 

This is the function that is executed when a consumer calls the subscribe() method. The subscriber function defines how to obtain or generate values or messages to be published.

 -이 Observable 인스턴스를 생성하는 함수는 소비자가 subscribe() method를 호출할 때 실행되는 함수입니다. 

 

The subscriber function는 메시지를 전달하거나 값을 생성하거나 값을 얻는 방법을 정의합니다

이렇게 생성된 옵저버블을 실행하고 알림 수신을 시작하기 위해선 해당 옵저버블의 subscribe() 메서드를 호출하여 관찰자를 전달합니다. 

 

게시자 -> 나였어...

-> function subscriber(){ new Observable();} 소비자가 subscribe() 메서드를 호출할 때 실행됨?

->  소비자가 subscribe() 실행 시 옵저버블에게 옵서버를 전달함 그러면 알림을 수신받을 수 있음.

->  subscribe()는 수신한 알림에 대한 핸들러를 정의하는 것.  (알림을 받은 후 소비자가 취해야 하는 행동)

 

 

The subscribe() call returns a Subscription 이 메서드는 Subscription 객체를 반환하는데 이는 메시지 전달을 중단할 수 있는 unsubscribe() 메서드를 가지고 있다.

 

 

예시를 좀 보자... 너무 말이 어렵다.

 

const locations = new Observable((observer) => {
  let watchId: number;

  // Simple geolocation API check provides values to publish
  if ('geolocation' in navigator) {
    watchId = navigator.geolocation.watchPosition((position: GeolocationPosition) => {
      observer.next(position);
    }, (error: GeolocationPositionError) => {
      observer.error(error);
    });
  } else {
    observer.error('Geolocation not available');
  }

  // When the consumer unsubscribes, clean up data ready for next subscription.
  return {
    unsubscribe() {
      navigator.geolocation.clearWatch(watchId);
    }
  };
});

 

1. 나는 지리 API를  이용해 값을 발행하는 옵저버블을 만들 것이다. 따라서 new Observable( (observer) => {}); 정의

 

2. 값을 발행하는 로직을 작성한다. 

 

3. 약속대로 리턴 값은 unsubscribe()를 가진 객체를 반환한다. 

 

const locationsSubscription = locations.subscribe({
  next(position) {
    console.log('Current Position: ', position);
  },
  error(msg) {
    console.log('Error Getting Location: ', msg);
  }
});

// Stop listening for location after 10 seconds
setTimeout(() => {
  locationsSubscription.unsubscribe();
}, 10000);

 

1. 클라이언트가  게시자(내)가 만든 (지리 API를 이용해 값을 주는) 옵저버블을 subscribe()를 호출한다.

 

2. 그때 클라이언트는 인자로 메시지를 전달받은 후 실행되는 콜백 함수를 넘긴다. 
즉 옵저버블의 정의를 보고 진행될 next(), error()를 사용하여 콜백을 정의하네..

 

3.  10초 뒤에 구독을 끊어 버린다. 

 

 

발생할 수 있는 흐름을 상상해보자.

1. 유저가 클릭함 -> 미리 정의된 옵저버블의 subscribe()를 실행시킴 -> 메시지 전달 후 콜백에서 처리됨 -> 구독을 끊어버림 -> 유저가 다시 클릭함 -> 아무 반응 없음. 

 

아 promise는 재호출을 지속적으로 하는 반면 옵저버블은 한번 구독 후 계속 호출해줄 필요가 없다는 이야기인가? 

Observable 객체는 이미 정의되어 있고, 이를 구독하면 정해놓은 로직이 실행될 것이고, 그 값을 전달해주면 콜백 함수에서 처리하도록 디자인되어있으니까?  

 

느낌에 비즈니스 로직과 비즈니스 로직을 호출하는 부분을 분리시켰다. 음... 

 

 

Observer

 

A handler for receiving observable notifications implements the Observer interface. It is an object that defines callback methods to handle the three types of notifications that an observable can send:

 

옵저버블이 전달한 메시지를 받는 핸들러는 Observer 인터페이스를 구현합니다.  이 핸들러는 subscribe() 메서드의 인자로 옵서버 인터페이스를 구현한 핸들러를 줄 수 있다.

 

예시에서 옵저버블은 옵서버를 인자로 전달받는데 비즈니스 로직 처리 후 값을 전달할 때 

observer.next() 메서드의 인자로 값을 전달해주는 것을 확인할 수 있다. 

 

 

옵서버 인터페이스를 구현해 옵저버블에게 전달하면 여러 조합을 정의하고 사용할 수 있을 것 같다. 

이러한 processor를 제공하지 않으면 관찰자는 해당 유형의 알림을 무시한다고 한다.

 

 

 

Subscribing

옵저버블은 누군가가 자신을 구독할 때만 메시지 발행을 시작한다. subscribe() 메서드는 메시지를 수신하기 위해 옵서버 객체를 전달하고 인스턴스의 메서드(옵서버 인터페이스의 메서드)를 호출하여 구독한다.

 

 

Tip

  • of(... items) Observable- 인수로 제공된 값을 동기적으로 전달하는 인스턴스를 반환합니다. (스트림 같네)
  • from(iterable)- 인수를 Observable인스턴스로 변환합니다. 이 방법은 일반적으로 배열을 오버 버블로 변환하는 데 사용됩니다. (이 트러블을 구현한 객체를 옵저버블로 반환하는 RxJS라이브러리 메서드인 것 같다.)

 

 

1,2,3이라는 메시지를 동기적으로 방출하는 옵저버블이다. 
비즈니스 로직이 1,2,3을 준다고 생각하면 될까?

const myObservable = of(1, 2, 3);

 

옵저버블은 옵서버 객체가 있어야 발행을 시작한다. 따라서 옵서버 인터페이스를 구현한 구현체를 만들어야 한다.

// Create observer object
const myObserver = {
  next: (x: number) => console.log('Observer got a next value: ' + x),
  error: (err: Error) => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

 

옵저버블에 옵서버를 주어 구독을 실행하면 동기적으로 옵저버블의 값을 발행해준다.

myObservable.subscribe(myObserver);

// Logs:
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification

 

또는 콜백 함수를 넘겨도 가능하다.

 

myObservable.subscribe(
  x => console.log('Observer got a next value: ' + x),  // next 구현
  err => console.error('Observer got an error: ' + err), // error 구현
  () => console.log('Observer got a complete notification') // complete 구현
);

 

Note that a next() function could receive, for instance, message strings, or event objects, numeric values, or structures, depending on context. As a general term, we refer to data published by an observable as a stream. Any type of value can be represented with an observable, and the values are published as a stream.

 

next() function은 string 타입의 메시지, 이벤트 객체, 숫자 값 혹은 구조체를 콘텍스트에 따라 전달받을 수 있다.

일반적인 용어로 우리는 옵저버블에 의해 발행된 데이터를 스트림이라고 언급한다. 

 

아! 한 가지 이해했다. 나는 옵저버블을 구독하고 메서드 체이닝으로 무언갈 처리하려고 했지만 메서드가 2개 정도밖에 없어 콜백 처리나 에러 처리를 어떻게 해야 할지 헤매고 있었다. 

 

이제 보니 next()의 전달된 값을 스트림으로 준다니까 옵서버 혹은 콜백 함수를 넘긴 후 전달받는 데이터에서 메서드 체이닝을 할 수 있지 않을까? 

또한 옵서버 객체를 옵저버블에게 인자로 전달할 때 complete()에 스트림이 전부 방출된 이후 진행할 콜백 함수 적으면 될듯한데,  설명에 따르면 이 핸들러는 실행이 완료된 후에도 계속해서 다음 핸들러로 전달될 수 있다고 한다. (무슨 말 인지 모르겠다.)

 

Creating observables

 

옵저버블을 만드는 방법 중 함수 선언식으로 subscribe() 함수가 호출된 이후 실행될 로직을 정의할 수 있다.

// This function runs when subscribe() is called
function sequenceSubscriber(observer: Observer<number>) {
  // synchronously deliver 1, 2, and 3, then complete
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();

  // unsubscribe function doesn't need to do anything in this
  // because values are delivered synchronously
  return {unsubscribe() {}};
}

// Create a new Observable that will deliver the above sequence
const sequence = new Observable(sequenceSubscriber);

 

예제를 좀 더 심화하여 이벤트를 게시하는 옵저버블을 만드는 예시를 보자

function fromEvent<T extends keyof HTMLElementEventMap>(target: HTMLElement, eventName: T) {
  return new Observable<HTMLElementEventMap[T]>((observer) => {
    const handler = (e: HTMLElementEventMap[T]) => observer.next(e);

    // Add the event handler to the target
    target.addEventListener(eventName, handler);

    return () => {
      // Detach the event handler from the target
      target.removeEventListener(eventName, handler);
    };
  });
}

 

1. HTMLELementEventMap의 속성 타입을 이벤트 이름으로 받도록 되어 있고, 

리턴 값으로 옵저버블을 생성한다. 인자로는 옵서버 객체를 받고 구독자에게 이벤트 객체를 전달하는 핸들러는 만들었다.

 

2. 이후 HTMLElement에 이벤트 리스너를 등록하는데, 전달받은 이벤트와 핸들러를 통해 등록한다.

 

3. 리턴 값으로는 이벤트리스너를 지우는 함수 리턴

 

다음과 같이 사용할 수 있다.

const ESC_CODE = 'Escape';
const nameInput = document.getElementById('name') as HTMLInputElement;

const subscription = fromEvent(nameInput, 'keydown').subscribe((e: KeyboardEvent) => {
  if (e.code === ESC_CODE) {
    nameInput.value = '';
  }
});

 

 

  • 함수는 호출에 따라 동기적으로 단일 값을 반환하는 게으른 연산입니다.
  • 제너레이터는 이터레이션에 따라 0개부터 무한에 가까운 수의 값을 반환할 수 있는 게으른 연산입니다.
  • 프로미스는 단일을 값을 미래의 특정 시점에 반환하거나, 아예 반환하지 않을 수도 있는 연산입니다.
  • 옵저버블은 호출되는 시점에 동기적으로, 혹은 미래의 특정 시점에 비동기적으로 0개부터 무한에 가까운 수의 값을 반환할 수 있는 게으른 연산입니다.

 

 

반응형

'FrontEnd > Angular' 카테고리의 다른 글

ngIf  (0) 2022.02.17

댓글