RxJS는 Reactive Extensions For JavaScript 의 약자로 reactive programming을 JS에서 지원하기 위해 만들어진 라이브러리다.

reactive programming: 데이터의 흐름과 변화에 반응하는 프로그래밍 패러다임이다. 
이 패러다임은 데이터의 변화를 이벤트로 간주하고, 이러한 이벤트에 대한 반응을 정의함으로써 시스템을 구축한다. 
특히 비동기적인 데이터 스트림을 처리하는 데 유용하며, 주로 사용자 인터페이스, 실시간 데이터 처리, 비동기 IO 작업 등에 적용된다.

이 리액티브 프로그래밍은 Push 시나리오 방식으로 외부와 통신한다.

Push 시나리오: 외부에서 명령하면서 응답이 오면 그때 반응하여 처리한다. 
    데이터를 가지고 오기 위해서는 subscribe를 해야한다.

RxJS는 이러한 비동기 이벤트 기반의 프로그램 작성을 돕기 위해 함수형 프로그래밍을 이용해 이벤트 스트림을 Observable이라는 객체로 표현한다.


Observable은 event가 흐르는 stream이다. Observable은 누군가 구독(subscribe)을 해야 event를 발행(publish)한다.
Observer가 Observable을 구독하면서 next, error, complete 키워드를 사용해 Observable에 흐르는 event를 처리한다.

    const observable$ = interval(1000).pipe(take(4));

Observable 변수에 붙은 $(달러) 표시는 Observable을 나타내는 코드 컨벤션이다.
interval()은 정의된 시간마다 증가하는 연속값을 stream에 발생시키고,
pipe() operator를 사용하여 Observable stream 내부에서 적용할 operator를 처리하게 된다.
take는 발생시킨 이벤트 중 처음부터 n개까지의 이벤트만 받는다.

ReactX는 크게 세 요소로 구성된다.

  1. Observable:
    일련의 값들을 발행한다. 관찰될 수 있는 것, 관찰되는 대상이란 뜻. 아래의 코드에서는 1에서 20까지의 정수를 반환한다.
    이렇게 연속적으로 발행되어 나오는 값들을 흐름, stream이라고 부른다.
  2. Operator:
    순수 함수들의 모임
  3. Observer:
    파이프만 쳐다보며 값을 기다리다가 뭔가 나오는 대로 최종 작업을 시행한다.
    옵저버가 파이프를 주시하며 발행물을 기다리는 것을 ReactiveX에서는 subscribe, 구독한다고 표현한다.
    구독자가 발행물들에 '반응'하는 것 이다.
const {range} = rxjs
cosnt {filter, take, map, toArray} = rxjs.operators

range(1,20)
    .pipe(
        filter(n => n % 2 === 0),
        take(5),
        map(n => Math.pow(n,2)),
        toArray(),
        map(arr => arr.join(', '))
    )
    .subscribe(console.log)
  1. Operator
    Operator는 Observable에서 각 이벤트들에 대해 연산을 할 수 있는 pure function이다.
    앞서 언급한 것처럼, RxJS는 함수형 프로그래밍에 영향을 많이 받아 이러한 pure function들이 많이 존재한다.
    대표적으로 tap(), filter(), min(), max()와 같은 operator가 존재한다.

    • tab(): 데이터 스트림의 요소에 대해 부수적인 작업을 수행할 때 사용된다. 스트림의 요소를 변경하지 않고, 디버깅이나 로깅 등에 유용하다.

      import { of } from 'rxjs';
      import { tap } from 'rxjs/operators';
      
      // of : RxJS에서 제공하는 함수로, 주어진 인수들을 순차적으로 방출하는 Observable을 생성한다.
      of(1, 2, 3).pipe(
      tap(x => console.log(`Tapped: ${x}`))
      ).subscribe();
    • filter(): 조건을 만족하는 요소만 스트림에 남겨둔다. 스트림의 각 요소에 대해 조건을 평가하여 true인 경우만 방출

      import { of } from 'rxjs';
      import { filter } from 'rxjs/operators';
      
      of(1, 2, 3, 4).pipe(
        filter(x => x % 2 === 0)
      ).subscribe(console.log); // 2, 4
    • min(): 스트림에서 최소값을 찾는다. 스트림의 모든 요소를 비교하여 가장 작은 값을 방출

       import { of } from 'rxjs';
       import { min } from 'rxjs/operators';
      
       of(5, 3, 9, 1).pipe(
         min()
       ).subscribe(console.log); // 1
    • max(): 스트림에서 최대값을 찾는다. 스트림의 모든 요소를 비교하여 가장 큰 값을 방출

       import { of } from 'rxjs';
       import { max } from 'rxjs/operators';
    
       of(5, 3, 9, 1).pipe(
         max()
       ).subscribe(console.log); // 9
  2. Observer
    Observer는 Observable을 구독하는 대상이다. Observer를 정의하고
    next, error, complete 세 가지를 정의해 주고 Observable에 구독을 하면 완성이다.

    • next: Observable에 들어오는 event를 처리한다.

    • error: Observable에서 error가 발생했을 때 event를 처리해 준다.

    • complete: Observable이 종료되면 complete가 호출되게 된다.

      예1

        const observable$ = interval(1000).pipe(take(4));
      
        const observer = {
            next: (item: number) => console.log(item),
            error: (error: Error) => console.log(err),
              complete: () => console.log('complete'),
        };
      
        observable$.subscribe(observer);

      예2

        import {fromEvent} from 'rxjs'
      
        // fromEvent를 이용해서 이벤트를 observable 객체로 만든다.
        // dom 요소와 click과 같은 이벤트
        const observable = fromEvent(document, 'click');
        // subscriber(이벤트핸들러)를 정의
        const subscriber = () => console.log('Clicked!');
        // observable가 subscribe를 사용해 이벤트 핸들러인 subscriber을 구독하도록 한다.
        observable.subscribe(subscriber)
    1. Observable
      Observable 객체는 특정 객체를 관찰하는 이벤트 핸들러인 Subscriber에게 여러 이벤트나 값을 보내는 역할을 한다.
아래의 코드는 RxJS의 Observable을 직접 생성하고 구독하는 예제이다.
observable이 subscriber에게 연속으로 1,2,3을 방출하고 1초 후에 4를 방출하도록 되어있다.  

```javascript
    const observable = new Observable(subscriber => {
    subscriber.next(1)  
    subscriber.next(2)
    subscriber.next(3)
    setTimeout(() => { 
      subscriber.next(4)
      subscriber.complete() // 스트림을 완료한다. 
    }, 1000)
  })

   // 1.
  console.log('just before subscribe')
  observable.subscribe({
    next(x) { // 새로운 값이 방출될 때 마다 호출된다.
      // 2.
      console.log('got value ' + x)
    },
    error(err) { 
      console.error('something wrong occurred: ' + err)
    },
    complete() { // 스트림이 완료될 때 호출된다.
      // 4
      console.log('done')
    },
  })
    // 3
  console.log('just after subscribe')
```

출처

https://www.youtube.com/watch?v=KDiE5qQ3bZI
https://velog.io/@stop7089/NestJS-%EC%99%80-RxJS

'NestJS > 모르는 것들' 카테고리의 다른 글

Guard, Strategy, Passport, Jwt  (0) 2024.06.25

프로그래밍 패러다임이란?

간단하게 프로그래밍의 스타일을 뜻한다. 프로그램은 순차, 분기, 반복, 참조로 구성되는데 이를 어떤 관점(스타일)을 중심적으로 설계하느냐에 따라 패러다임의 종류가 나뉜다. 다시 말하면, 프로그래밍 패러다임은 개발자로 하여금 프로그래밍을 할 때 관점을 제시해주는 역할을 한다.

현재 프로그래밍 패러다임은 많은 종류가 있다.
크게 명령형과 선언형 프로그래밍으로 나뉜다.

명령형 프로그래밍은 컴퓨터에 문제를 어떻게 해결하는지 명령을 내리는 방식으로, 대표적으로 절차지향 프로그래밍객체지향 프로그래밍이 있다.

선언형(함수형) 프로그래밍은 컴퓨터에게 무엇을 해야하는지를 선언해주는 방식으로, 함수형에서 어떻게 해결하는지를 설명한 방법을 변수에 담을 수 있기 때문에 그 방법이 담긴 변수만 무엇인지만 명시해주면 된다.



절차지향 프로그래밍(Procedural Programming)

절차지향(절차적) 프로그래밍이란 프로그램을 함수나 절차의 집합으로 구성하는 패러다임이다. 일련의 명령문을 순차적으로 실행하는 방식으로 프로그램을 작성한다.


def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def main():
    x = 10
    y = 5
    print("Add:", add(x, y))        # Add: 15
    print("Subtract:", subtract(x, y))  # Subtract: 5

if __name__ == "__main__":
    main()

함수 호출 중심이란 전체 로직에서 재사용성이 높은(재사용이 가능한) 코드들을 별도의 함수로 분리하여 필요할 때마다 해당 함수를 호출하는 방식의 프로그래밍을 뜻한다.

TOP-DOWN 방식의 프로그래밍이다. 이러한 방식은 추상적인 개념을 구체화해 나아가는 방식이다. 예를 들어 *"핸드폰으로 사진을 찍는다"* 라는 추상적인 상황을 *"카메라 앱을 선택한다" -> "원하는 모드를 선택한다." -> "초점을 맞춘다" -> "셔터와 버튼을 누른다"* 와 같이 구체화해 나아가는 방식을 말한다.

절차지향 프로그래밍에서는 Main 함수가 하나의 큰 개념이라고 보면, 그 안에서 함수를 호출하고, 조건문을 사용하는 등을 통해서 개념을 구체화해 나간다.

절차지향 프로그래밍은 순차적으로 함수를 불러오기 때문에 함수의 역할만 알면 전반적인 프로그램의 목적을 이해하기 쉽다. 반대로 프로그램이 복잡해질수록 함수가 많아지지만 데이터는 main에서 관리하므로 프로그램의 방향성을 이해하기 어려워지며 유지보수도 어려워진다.



객체 지향 프로그래밍(Object-Oriented Programming, OOP)

객체지향 프로그래밍이란 객체를 최소 단위로 두며 이 객체에 책임을 명확히 해 각각 객체끼리 상호작용을 하도록 설계하는 프로그래밍 패러다임이다. 절차지향에서 처럼 main에서 프로그램의 진행을 다루는 것이 아니라 모든 것을 객체라고 생각하고 객체끼리의 상호관계를 통해서 일을 처리하는 방식이다.

 class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("Subclass must implement this method")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak())  # Buddy says Woof!
print(cat.speak())  # Whiskers says Meow!

절차지향과 다르게 객체지향 프로그래밍은 각각의 객체마다 각각의 데이터와 메소드(함수)를 가지고 있다. 각각의 데이터와 메소드(함수)는 경우에 따라 외부에서 접근할 수 있기도 하며 보호되기도 한다.

프로그래밍은 전체적으로 객체와 객체 간의 메서드 호출로 이루어진다. 그리고 각 객체는 자신의 기능을 수행하는데 필요한 데이터를 직접 가지고 있다.

객체지향 프로그래밍은 다형성, 의존성 주입 등의 특징으로 코드를 쉽게 확장시킬 수 있어 여럿이서 협력할 때 사용하기에 좋다.

객체지향 프로그래밍은 직관적이며, 유지보수가 쉽다. 반면에 객체에 대한 이해가 없다면 코드를 이해하기 어려울 수 있다. 왜냐하면 입력에 따라서 다양한 작업으로 진행되기 때문이다.

주요 특징

  • 클래스와 객체: 클래스는 객체의 청사진이며 객체는 클래스의 인스턴스이다.
  • 캡슐화: 객체의 상태를 보호하고, 객체의 내부 구현을 숨기는 것을 의미한다.
  • 상속: 새로운 클래스가 기존 클래스의 특성과 행동을 재사용할 수 있도록 한다.
  • 다형성: 동일한 인터페이스를 사용하여 서로 다른 데이터 타입을 처리할 수 있다.



함수형 프로그래밍(Functional Programming)

함수형 프로그래밍은 함수가 최소 단위이며 외부 상태를 갖지 않는 함수들을 통해 파이프 형태로 프로그래밍을 하는 패러다임이다. 또한 함수형 프로그래밍은 모든 데이터의 처리를 수학적 함수로 취급하여 결과 값을 구하는 방식의 프로그래밍이다.

여기서 외부 상태를 갖지 않는다는 의미는 같은 입력을 넣었을 때 언제나 같은 출력을 내보낸다는 것이다. 다시 말하면 어떤 외부 요인도 함수의 입출력에는 영향을 주지 않는 다는 것이다. 통제하지 못하는 외부 상태를 사용한다면 예측하지 못하는 부작용을 가질 수 있기에 외부 상태를 수정할 수 없도록 하는 것이 중요하다.

// 순수 함수: 동일한 입력에 대해 항상 동일한 출력을 반환
const add = (a, b) => a + b;

// 고차 함수: 함수를 인수로 받거나 함수를 반환하는 함수
const applyFunction = (f, x, y) => f(x, y);

// 불변성: 데이터가 변경되지 않으며, 데이터 변경 시 새로운 데이터를 생성한다.
const originalArray = [1, 2, 3];
const newArray = [...originalArray, 4];

// 합성: 작은 함수를 조합하여 복잡한 연산을 구성
const increment = x => x + 1;
const double = x => x * 2;

const incrementAndDouble = x => double(increment(x));

console.log(applyFunction(add, 10, 5));  // 15
console.log(originalArray);  // [1, 2, 3]
console.log(newArray);  // [1, 2, 3, 4]
console.log(incrementAndDouble(2));  // 6

함수형 프로그래밍 코드에서는 한 번 초기화한 객체 및 변수는 변하지 않는다. 이러한 특성을 불변성이라고 하는데, 이 불변성 때문에 프로그램의 동작을 예측하기 쉽고 프로그램의 데이터 흐름이 유지될 수 있다.

함수형 프로그래밍은 주어진 문제를 잘게 쪼개 그 문제를 해결하는 함수를 만들고 그 함수들을 결합하는 방식으로 문제를 해결해 나간다.

외부 상태로 인한 부작용이 없기에 안정적이다. 그러므로 동시성을 가지는 프로그램(대용량 데이터를 병렬적으로 처리하는 경우)에 사용하기 적합하다. 하지만 온전히 함수형 프로그래밍으로 구성하기 위해선 정말 다양한 함수들을 조합해 사용해야 한다는 단점이 있다.



반응형 프로그래밍(Reactive Programming)

비동기 데이터 흐름에 기반을 둔 프로그래밍 패러다임으로, 데이터 중심 사고 방식인 프로그래밍이다. 이벤트나 변화에 반응하기 위한 비동기적 데이터 처리 및 흐름 기반 프로그래밍 패러다임이다. 이것은 프로그래밍 언어로 정적 또는 동적인 데이터 흐름을 쉽게 표현할 수 있어야하며, 데이터 흐름을 통해 하부 실행 모델이 자동으로 변화를 전파할 수 있다는 것을 의미한다.

 

데이터와 데이터 스트림에 영향을 미치는 모든 변경 사항을 관련된 모든 당사자들에게 전파하는, 모든 프로그램을 reactive 프로그램이라고 할 수 있다. 다음과 같은 이점이 있다.

  • 간결해진 thread 사용
  • 간단한 비동기 연산
  • 콜백 지옥의 제거

 

반응형 프로그래밍을 가장 쉽게 이해할 수 있는 예1.

| - | A | B |C |D |
|---|---|---|
| 1열 | 1 | 2 |=A1+B1 |=A1+B1+C1 |

  • C와 D 열에는 수식을 선언적으로 작성해 둔다.
  • A1, B1의 값을 변경하면 즉시 변경사항이 전파 -> C1의 값이 자동으로 변경됨
  • C1이 변경되자, D1에도 수식이 반영되어 값이 변경됨
  • 어떤 순서로 데이터가 변경되어야 하는지 정해주지 않았음에도 하나의 데이터의 흐름이 만들어짐

 

예2.

// 명령형 프로그래밍 - 결과값이 저장
int a=10, b=20;
int c = a+b;
System.out.println(c)); // c = 30;
a = 20;
System.out.println(c)); // c = 30; c에 계산식의 결과값 30만 저장됨

// 반응형 프로그래밍 - 계산식이 저장
int a=10, b=20;
int c = a+b;
System.out.println(c)); // c = 30;
a = 20;
System.out.println(c)); // c = 40; c에 계산식 자체가 저장되어 사용하는 변수 a의 값이 바뀐 것이 반영됨

OOP와 다르게 데이터의 흐름에 더 집중한다. 이 데이터 흐름은 관찰할 수 있거나 필터링하거나 다룰 수 있으며 새로운 흐름을 만들기 위해 다른 흐름과 병합할 수도 있다.

 

직접 재계산명령이 필요없다. 변수 값을 바꾸면 해당 변수를 참조하는 모든 식들이 연쇄적으로 재평가되면서 스스로의 값을 갱신한다.

출처

https://ontheway.tistory.com/69
https://velog.io/@ehgus8621/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84

'인터넷 기본 지식' 카테고리의 다른 글

#9. RESTful 웹 API 디자인  (1) 2024.06.19
#8. LSP  (2) 2024.06.10
#6. DNS(Domain Name System)  (0) 2024.05.31
#5. 웹 브라우저는 어떻게 작동하는가?  (0) 2024.05.31
#4 WAS, 웹 서버  (0) 2024.05.31

binding이란?

this를 binding한다. 이 말이 무슨 뜻일까?
javascript의 함수는 각자 자신만의 this라는 것을 정의한다. 예를 들어 자기소개를 하는 함수를 위해 say()이라는 함수를 만든다고 하자.

const say = function(){
    console.log(this);
      console.log("Hello, my name is" + this.name);
}

say();

실행결과는 다음과 같다.

window객체가 나타난다. 기본적으로 this는 window이기 때문이다.
하지만 무조건적으로 window라고 볼 수는 없다. this는 객체 내부, 객체 메서드 호출 시, 생성자 new 호출 시, 명시적 bind 시에 따라 바뀌기 때문이다.

this의 binding을 통해서 this를 원하는, 알맞은 객체로 바꿔줄 수 있다.
명시적으로 위의 this를 window가 아닌 다른 객체로 바꿔주는 함수가 call, apply, bind이다.

call과 apply

원래 함수는 선언한 후 호출해야 실행된다.
호출하는 방법으로는 함수 뒤에 ()를 붙이는 것, 그리고 call과 apply하는 방법이 있다.

다음은 say 함수이다.

cosnt say = function(city){
    console.log(`Hello, my name is ${this.name}, I live in ${city}`);
} 

const obj = {name:"Tom"};

say("seoul"); // Hello, my name is , I live in seoul
say.call(obj, "seoul"); // Hello, my name is Tom, I live in seoul
say.apply(obj, ["seoul"]); // Hello, my name is Tom, I live in seoul

call과 apply는 함수를 호출하는 함수이다. 첫 번째 인자에 this로 설정하고 싶은 객체를 넘겨주어 this를 바꾸고나서 실행한다.

첫 번째 실행의 경우 this에 아무런 setting이 되어있지 않으므로, this는 window 객체이다. 두 번째, 세 번째 실행의 경우 this를 obj로 변경시켰으므로 원하는 값이 나온다.

call과 apply의 첫 번째, this를 binding할 값을, 나머지에는 실제 say에 필요한 parameter를 입력하는 방식이다. call과는 다르게 apply함수는 두 번째 인자부터 모두 배열에 넣어야 한다.


또 다른 예이다.

var obj = {
  string: 'zero',
  yell: function() {
    console.log(this.string);
  }
};
var obj2 = {
  string: 'what'
};

obj.yell(); // 'zero';
obj.yell.call(obj2); // 'what', obj1.yell()을 obj2.yell()로 바꾼 효과라고 보면 된다
obj.yell.apply(obj2); // 'what'

위의 코드의 obj.yell.call(obj2)로 this가 가리키는 것을 obj에서 obj2로 바꾸었다.

yell은 obj의 method임에도 불구하고 zero 대신 what으로 변경되었다.

즉 call을 써서 this를 정의해준다면 다른 객체의 parameter나 method를 자기 것마냥 사용할 수 있다.

<팁!>

  • .call(this, paramter1, parameter2, ...)
  • .apply(this, [paramter1, parameter2, ...])

bind

bind 함수가 call, apply와 다른 점은 함수를 실행하지 않는다는 점이다. 대신 bound 함수를 리턴한다. 이 bound 함수는 this를 설정한 함수이다. 또한 함수에 argument를 넣어 줄 수 있다.

const obj = {name: "nanana"};

const say = function(city){
    console.log(`Hello, my name is ${this.name}, I live in ${city}`);
}

const boundSay = say.bind(obj);
const boundSayTwo = say.bind(obj,"busan");

boundSay("seoul");
boundSayTwo();

call, apply, bind 정리

call, apply, bind는 함수 호출 방법을 지정해서 this를 그때 그때 알맞은 객체로 명시적으로 바꿔주는 메소드이다.

call, apply와 bind의 차이점

  • call, apply는 함수를 호출해서 인수 전달.
  • bind 함수는 호출하지 않는다. this값을 유지하는 새로운 함수를 생성

call과 apply의 차이점

  • call의 경우, 호출한 함수의 인수를 ,로 구분한 리스트 형식으로 전달
  • apply는 배열 [ ]로 전달

출처

https://wooooooak.github.io/javascript/2018/12/08/call,apply,bind/
https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-Call-Bind-Apply
https://velog.io/@dev_0livia/JavaScriptcall-apply-bind%EC%9D%98-%EC%B0%A8%EC%9D%B4

'JS' 카테고리의 다른 글

Async/Await  (0) 2024.06.12
동기, 비동기  (1) 2024.06.12
Call back 함수  (1) 2024.06.11

+ Recent posts