오늘 소개할 부분은 작성 중인 책의 “부록” 중 일부이다.
이 장에서는 RxJS Scheduler를 잘 사용하기 위해서 이해해야할 자바스크립트 비동기 처리 과정을 살펴본다.

RxJS를 모르는 사람이라도 자바스크립트의 비동기 처리과정을 이해하면 자바스크립트를 개발하는데 정말 많은 도움을 준다.
더 자세한 내용은 다음 URL을 참고하여 꼭! 읽어보기 바란다

참고 URL


부록. RxJS Scheduler와 자바스크립트 비동기 처리 과정의 이해

RxJS Scheduler는 RxJS에서 자바스크립트의 비동기 작업을 효과적으로 처리할수 있도록 도와주는 역할을 한다.
따라서, RxJS Scheduler를 잘 활용하기 위해서는 기본적으로 자바스크립트 엔진이 어떻게 비동기 작업을 처리하는 지를 알면 RxJS Scheduler를 사용하는데 많은 도움이 된다.

이 장에서는 자바스크립트 엔진이 어떻게 비동기 작업을 처리하는 지를 살펴보고, 각 비동기 작업에 해당되는 RxJS scheduler는 어떤 것이 있는지 살펴보기로 하자.

Continue reading

오늘 소개할 부분은 작성 중인 책 2부의 개론에 해당하는 내용이다.
이 장을 통해 RxJS의 개발과정의 큰 그림을 다시한번 살펴보기 바란다


1부에서는 RxJS의 본질을 알아가기 위해 RxJS가 고민했던 문제들을 살펴보았다. 2부에서부터는 RxJS 라이브러리에 대해 자세히 알아보자. 이 장을 통해서는 RxJS로 간단한 소스를 구현해보면서 RxJS의 사용법을 익혀보도록 하자.

RxJS

RxJS의 공식 사이트에서는 RxJS에 대해 다음과 같이 정의하고 있다.

RxJS is a library for composing asynchronous and event-based programs by using observable sequences.
RxJS는 Observable를 사용하여 비동기 및 이벤트 기반 프로그램을 작성하기 위한 라이브러리이다.

1부에서 필자가 정의한 범용적인 데이터 플로우 솔루션을 지향하는 라이브러리의 국소적인 표현이라고 할수 있다. 특이한 것은 공식 홈페이지에는 RxJS에 대해 이벤트용 lodash 정도라고 생각해라라는 말도 있다.

Think of RxJS as Lodash for events.

앞의 용어가 RxJS의 철학에 대한 정의라면, 뒤의 정의는 실제 사용에 대한 정의라고 볼수 있다.
RxJS가 어렵다면 지금은 그냥 비동기 Array/Collection 데이터를 다루는 라이브러리 정도로 생각하고 접근해보자.

Continue reading

오늘 소개할 부분은
웹어플리케이션 개발시 발생할 수 있는 로직 오류에 대한 문제를 RxJS는 어떻게 접근했는지에 대한 이야기이다.


웹어플리케이션의 로직

웹어플리케이션은 로직에 근거하여 전달받은 입력값을 이용하여 새로운 결과를 반환하거나 표현한다.
여기서 로직은 산술적인 로직이 될 수 있고 비즈니스적인 로직이 될수 있다. 또는 if문과 같이 간단한 프로그램의 흐름을 담당하는 부분일 수도 있다.

화면에 사용자 정보를 표현하는 UI 작성하는 예를 생각해보자. (예제에서 사용하는 API는 스타워즈 등장인물을 조회한다)
DB로부터 조회한 사용자 목록 데이터가 입력값이라면 이 값을 바탕으로 우리는 다양한 처리를 한다.

  • 성별이 “남”과 “여”인 사용자만 추출한다 (스타워즈 등장인물은 로봇과 같이 성별이 없는 사용자도 있다)
  • 사용자의 이름, 키, 몸무게를 표시한다.
  • 사용자의 성별에 맞게 아이콘을 화면에 표시한다.
  • 사용자의 표준 체중을 계산하여 표시한다.

    BROCA 방식

    • 남자 표준체중 = (키 - 100) × 0.9
    • 여자 표준체중 = (키 - 105) × 0.9

    BMI 방식

    • 남자 표준체중 = 키/100 _ 키/100 _ 22
    • 여자 표준체중 = 키/100 _ 키/100 _ 21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
const jsonData = JSON.parse(xhr.responseText);
document.getElementById("users").innerHTML = process(jsonData);
}
};
xhr.open("GET", "http://swapi.co/api/people/");
xhr.send();

// 데이터를 처리하는 함수
function process(people) {
const html = [];
for (const user of people.results) {
if (/male|female/.test(user.gender)) {
let broca;
let bmi;
if (user.gender == "male") {
broca = (user.height - 100 * 0.9).toFixed(2);
bmi = ((((user.height / 100) * user.height) / 100) * 22).toFixed(2);
} else {
broca = (user.height - 100 * 0.9).toFixed(2);
bmi = ((((user.height / 100) * user.height) / 100) * 21).toFixed(2);
}
const obesityUsingBroca = (((user.mass - broca) / broca) * 100).toFixed(
2
);
const obesityUsingBmi = (((user.mass - bmi) / bmi) * 100).toFixed(2);

html.push(`<li class='card'>
<dl>
<dt>${user.name} <i class="fa fa-${user.gender}"></i></dt>
<dd><span>키 : </span><span>${user.height} cm</span></dd>
<dd><span>몸무게: </span><span>${user.mass} kg</span></dd>
<dd><span>BROCA 표준체중 : </span><span>${broca} kg</span></dd>
<dd><span>BROCA 비만도 : ${obesityUsingBroca}</span></dd>
<dd><span>BMI 표준체중 : </span><span>${bmi} kg</span></dd>
<dd><span>BMI 비만도 : ${obesityUsingBmi}</span></dd>
</dl>
</li>`);
}
}
return html.join("");
}

원래 조회했던 데이터는 온데 간데 없고 그 데이터로부터 생산된 새로운 정보들을 사용하고 있다.
위 예제는 사용자 체중과 키를 이용하여 성별에 따라 비만도 값을 계산하고 성별 아이콘을 화면에 표시한다.

우리는 알게 모르게 데이터를 추출하고 변환하는 작업을 빈번하게 하고 있다.
복수 데이터를 처리하기 위해서는 반복문을 사용하고, 상황에 따라 데이터를 추출하거나 접근하기 위해서 분기문을 사용한다.
또한, 상황을 기억하거나 추출된 정보를 임시로 기억하기 위해서 변수를 사용한다.

로직의 복잡성 그리고 오류

반복문과 분기문 그리고 변수는 우리 코드를 복잡하게 만든다. 반복문은 우리 코드의 가독성을 떨어뜨리고 분기문은 우리가 확인해야할 프로그램의 흐름을 여러 개로 만든다. 더군다나 우리가 기억해 놓은 변수의 값은 누군가에 의해 변경될 수 있다. 변수의 값이 변하면 우리가 의도했던대로 흐름으로 프로그램이 동작하지 않을 수 있다.
이렇게 반복문과 분기문 그리고 변수는 우리 코드의 복잡도를 높이고 가독성을 떨어뜨리고, 결국에는 오류의 발생 빈도를 높인다.

반복문과 분기문

로직의 복잡성을 줄이는 가장 간단한 방법으로는 기능을 쪼개는 것이다. 기능별로 쪼갠다는게 단순히 구역별로 쪼개게 되면 기능의 의미를 명확하게 드러내지 못한다. 더불어 이런 코드는 재사용성을 떨어뜨린다.

이렇게 기능을 쪼개는 일이 쉬운 일이 아닌 이유는 코드의 대다수는 다음과 같이 로직과 반복문, 분기문의 결합으로 구성되어 있기 때문이다.

코드에서 반복문과 분기문을 모두 제거한다는 것은 사실상 불가능하다. 하지만 기능 단위로 분리 할 수 있다면 기능을 추상화 할 수 있고, 이로 인해 로직의 복잡성을 줄일 수 있다.

변수는 오류의 시작

변수를 사용한다는 의미는 오류를 발생시킬 수 있는 확률을 높일 수 있다. 변수는 변경될 수 있는 값이기 때문에 유용하다. 반면, 의도치 않게 이 값이 바뀔 경우에 우리는 오류에 직면하게 된다.
브라우저 환경의 자바스크립트에서는 싱글 쓰레드 구조이기 때문에 Mutil Thread의 사용으로 인한 동시성 문제는 자주 발생하지 않는다. 하지만 DOM에 등록된 이벤트 핸들러로 인해 변수의 값이 변경되거나 비동기 행위로 인해 외부로 노출된 변수의 값들이 변경 될 수 있다.

WebWorker와 같은 기술 스펙을 사용하면 Mutil Thread 기술을 사용할 수 있지만 브라우저는 기본적으로 하나의 메인 스레드에서 모든 작업이 이루어 진다.

따라서, 우리는 변수의 노출 범위를 제한하거나 제거함으로써 변수의 값이 외부에 의해 변경되지 않고 개발자의 의도에 따라 정확하게 변경될 수 있도록 보장하여만 한다.

자바스크립트의 솔루션

다행히도 자바스크립트는 이런 면에서는 꽤나 훌륭한 솔루션을 제공하고 있다.
함수형 프로그래밍의 특성을 가진 자바스크립트 함수를 이용하면 실제 로직과 상관이 없는 반복문, 분기문을 분리할 수 있다. 더불어 변수 또한 제거해 나갈 수 있다.
이렇게 함으로써 로직의 의미를 더욱 명확히 할 수 있으며 재사용성을 더욱 높일 수 있다.

자바스크립트 함수는 일급객체이다.
일급 객체(First-class object)는 다음과 같은 특성을 가지고 있다.

  • 변수 혹은 데이터 구조에 저장할 수 있다
1
2
> var savedFunction = function() {};
>
1
2
3
4
5
6
> - 파라미터로 전달할 수 있다.
> ```js
function foo(f, value) {};
foo(function() {
console.log("함수를 파라미터로 전달 할 수 있다");
}, "값");
  • 반환값으로 사용할 수 있다.
1
2
>  function foo() {
>
return function() {
   console.log("함수를 반환할 수 있다");
};

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

#### 로직의 분리
앞의 process 함수를 기능 단위의 로직과 반복문, 분기문으로 분리해보자.
process 함수는 다음과 같은 구조로 되어 있다.

{% asset_img advance_logic.png %}

여기에서 우리의 주요 관심사는 성별에 따라 비만도를 구하는 로직과 사용자별 HTML을 만드는 로직이다.
이 부분을 별도의 함수로 만들어보자.

표준 체중과 비만도를 계산하는 함수는 height, mass, gender을 입력값으로 받아서 BROCA와 BMI 방식의 비만도와 표준 체중을 반환한다.
```js
// 표준 체중과 비만도를 계산하는 함수
function logic(height, mass, gender) {
let broca = (height - (gender === "male" ? 100 : 105)) * 0.9;
let bmi = height / 100 * height / 100 * (gender === "male" ? 22 : 21);
if (gender == "male") {
broca = (height - 100 * 0.9).toFixed(2);
bmi = (height / 100 * height / 100 * 22).toFixed(2);
} else {
broca = (height - 100 * 0.9).toFixed(2);
bmi = (height / 100 * height / 100 * 21).toFixed(2);
}
const obesityUsingBroca = ((mass - broca) / broca * 100).toFixed(2);
const obesityUsingBmi = ((mass - bmi) / bmi * 100).toFixed(2);
return {
broca,
bmi,
obesityUsingBroca,
obesityUsingBmi
};
}

사용자 정보별 HTML을 만드는 함수는 user 정보를 받아서 string 형태의 html을 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 사용자 정보를 표현하기 위해 HTML을 만드는 함수
function makeHtml(user) {
return `<li class='card'>
<dl>
<dt>${user.name} <i class="fa fa-${user.gender}"></i></dt>
<dd><span>키 : </span><span>${user.height} cm</span></dd>
<dd><span>몸무게: </span><span>${user.mass} kg</span></dd>
<dd><span>BROCA 표준체중 : </span><span>${user.broca} kg</span></dd>
<dd><span>BROCA 비만도 : ${user.obesityUsingBroca}</span></dd>
<dd><span>BMI 표준체중 : </span><span>${user.bmi} kg</span></dd>
<dd><span>BMI 비만도 : ${user.obesityUsingBmi}</span></dd>
</dl>
</li>`;
}

logic, makeHtml함수를 이용하면 다음과 같이 process 함수를 작성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
function process(people) {
const html = [];
for (const user of people.results) {
if (/male|female/.test(user.gender)) {
const result = logic(user.height, user.mass, user.gender);
Object.assign(user, result);
html.push(makeHtml(user));
}
}
return html.join("");
}

logic, makeHtml 함수를 만듦으로서 우리는 핵심 로직을 작성는데 집중할 수 있게 되었다.
더불어 logic, makeHtml 함수도 재사용 할 수 있는 단위 함수가 되었다.

반복문, 분기문, 그리고 변수와의 이별

위에 개선한 process도 좋은 코드이다. 우리는 구현 로직에 더 집중할 수 있게 되었다.
하지만, 흐름을 제어하는 반복문과 분기문은 여전히 process에 존재한다.
코드가 크면 클수록 process에 존재하는 반복문과 조건문은 우리 코드의 가독성을 떨어뜨릴 것이다. 더불어 html, result 같은 변수가 여전히 존재하기 때문에 우리는 항상 오류에 노출될 것이다.

이번에는 ES5에서 제공하는 Array의 filter, map, reduce와 같은 고차함수(High-order function)를 이용하여 process를 개선해 보자.

고차함수 (Higher-order function)

  • 다른 함수를 인자로 받거나 그 결과로 함수를 반환하는 함수.
    출처: wikipedia https://en.wikipedia.org/wiki/Higher-order_function
  • 고차 함수는 변경되는 주요 부분을 함수로 제공함으로서 동일한 패턴 내에 존재하는 문제를 손쉽게 해결할 수 있는 고급 프로그래밍 기법이다.
  • 고차 함수를 이용하면 함수의 합성, 변형과 같은 작업을 손쉽게 할수 있다. 더불어 Currying, Memoization과 같은 기법도 사용할 수 있다.
1
2
3
4
> const twice = (f, v) => f(f(v));
> const fn = v => v + 3;
> console.log(twice(fn, 7)); // 13
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

```js
function process(people) {
return people.results
.filter(user => /male|female/.test(user.gender))
.map(user => Object.assign(
user,
logic(user.height, user.mass, user.gender)
))
.reduce((acc, user) => {
acc.push(makeHtml(user));
return acc;
}, [])
.join("");
}

if문은 filter로 변환하고, 값을 변환해야하는 경우에는 map을 이용하고, 축적된 데이터를 반환해야하는 경우에는 reduce를 이용하였다.
각각의 고차함수에 전달되는 함수는 외부의 변수에 영향을 미치지도 않고, 영향을 받지도 않는 함수이다.
전달된 함수는 항상 같은 입력이 주어지면, 항상 같은 출력을 반환한다.
이런 함수를 함수형 프로그래밍에서는 순수함수라고 한다.

순수함수와 함수형 프로그래밍에 대한 내용은 부록. 함수형 프로그래밍 (Functional Programming)을 참조하기 바란다.

개선된 process에서는 반복문, 분기문, 변수가 존재하지 않는다.
핵심 로직은 분리되었고 코드의 흐름은 단일화되었다. 더불어 변수를 사용하지 않음으로서 오류의 발생 빈도도 크게 줄었다.

RxJS는 어떻게 개선하였나?

RxJS 또한 ES5 Array의 고차 함수와 같은 operator를 제공함으로써 로직에 존재하는 분기문과 반복문, 그리고 변수를 제거하려고 하였다.

Immutable 객체 Observable

ES5 Array의 고차함수들이 반환값으로 새로운 Array 객체를 반환하여 각각에 영향을 미치지 않도록 하는 것과 같이 RxJS의 operator는 항상 새로운 Observable을 반환함으로써 Array의 고차함수와 같이 불변 객체 (Immutable Object)를 반환한다.
불변 객체는 생성 후 그 상태를 바꿀 수 없는 객체이다. 불변 객체는 외부에서 값을 변경할 수 없기 때문에 불변 객체를 사용하는 것만으로도 프로그램의 복잡도가 줄어든다.

1
2
3
4
var arr = [1, 2, 3];
var mappedArr = arr.map(v => v);

console.log(arr === mappedArr); // false

Array와 다른점이 있다면 Array의 경우는 새로운 Array 객체 생성 작업만 하지만 Observable은 새로운 Observable를 만들고, 그 Observable이 operator를 호출한 Observable을 내부적으로 subscribe 하는 형태를 유지한다. 즉, Linked list 형태로 기존 Observable 객체와 새롭게 만든 Observable 객체를 operator로 연결하고 있다.

RxJS가 제공하는 Operator

다음은 공식 홈페이지에서 제공하는 operator 목록이다.
http://reactivex.io/rxjs/manual/overview.html#categories-of-operators

카테고리 operator
생성 operator ajax, bindCallback, bindNodeCallback, create, defer, empty, from, fromEvent, fromEventPattern, fromPromise, generate, interval, never, of,repeat ,repeatWhen, range ,throw ,timer
변환 operator buffer, bufferCount, bufferTime, bufferToggle, bufferWhen, concatMap, concatMapTo, exhaustMap, expand, groupBy, map, mapTo, mergeMap, mergeMapTo, mergeScan, pairwise, partition, pluck, scan, switchMap, switchMapTo, window, windowCount, windowTime, windowToggle, windowWhen
추출 operator debounce, debounceTime, distinct, distinctKey, distinctUntilChanged, distinctUntilKeyChanged, elementAt, filter, first, ignoreElements, audit, auditTime, last, sample, sampleTime, single, skip, skipUntil, skipWhile, take, takeLast, takeUntil, takeWhile, throttle, throttleTime
결합 operator combineAll, combineLatest, concat, concatAll, exhaust, forkJoin, merge, mergeAll, race, startWith, switch, withLatestFrom, zip, zipAll
멀티캐스팅 operator cache, multicast, publish, publishBehavior, publishLast, publishReplay, share
에러 처리 operator catch, retry, retryWhen
유틸리티 operator do, delay, delayWhen, dematerialize, finally, let, materialize, observeOn, subscribeOn, timeInterval, timestamp, timeout, timeoutWith, toArray, toPromise
조건.참거짓 operator defaultIfEmpty, every, find, findIndex, isEmpty
수학,누적 operator count, max, min, reduce

RxJS에서 제공하는 operator를 이용하면 Observable을 생성 할 수도 있고 전달된 데이터를 변환하거나 필요한 데이터만을 추출할 수 도 있다.
더불어 여러 개의 Observable을 합성하기도 하고, 하나의 Observable을 다른 여러개의 Observable로 나눌 수도 있다.

RxJS는 정말 많은 operator를 제공한다. operator의 의미에 대해 잘 아는 사람에게는 굉장히 편리하다. 반면 그 의미를 잘 알지 못하는 사람에게는 오히려 진입 장벽이 되기도 한다.
이 operator의 기본적인 철학은 함수형 프로그래밍에 그 근간을 두고 있다.
아마도 함수형 프로그래밍 언어를 배운 독자라면 꽤 익숙한 이름의 operator들이 있는 것 을 알 수 있다.

RxJS의 operator는 어휘와 같다.
내가 많은 단어와 문장을 알아서 사용하게 되면 나의 편의성이 증가하고 더불어 나의 품격도 높아질수 있다. 하지만, 다른 한편으로는 다른 사람이 내 말을 이해하기 어려워 할 수도 있다.
반면 내가 알고 있는 단어와 문장이 적다고 하더라도 의사소통이 될 정도의 단어와 문장을 사용한다면 생활하는데 부족함이 없다. 마찬가지로 RxJS의 모든 operator를 다 알 필요는 없다.
카테고리별로 자주쓰는 operator 몇 개를 잘 알고 적용할 수 있다면 충분히 RxJS의 장점을 극대화 할수 있다.
따라서, 이 책에서도 RxJS의 모든 Operator를 다루지는 않는다. 주요 카테고리별로 꼭! 알아야하는 RxJS의 Operator를 몇 개를 기준으로 설명을 할 예정이다.
자세한 내용은 다음 2부에서 진행하는 실제 프로젝트를 통해 조금씩 익혀나가 보자.

정리

웹어플리케이션의 로직은 반복문, 분기문, 변수에 의해 복잡도가 증가한다. 복잡도가 증가하게되면 이로 인해 코드의 가독성이 떨어지고 결국에는 오류에 직면하게 된다.
ES5 Array의 고차함수를 이용하면 반복문, 분기문, 변수를 로직으로부터 분리하고 제거할수 있다. 마찬가지로 RxJS는 ES5 Array의 고차함수와 같은 operator를 제공한다.
operator는 Immutable한 Observable를 항상 생성함으로써 외부나 내부에 영향을 미치지 않는다. 이런 구조는 오류의 발생 빈도를 낮추는 역할을 한다.

Comment and share

오늘 소개할 부분은
웹어플리케이션 개발시 발생할 수 있는 상태 전파 문제를 RxJS는 어떻게 접근했는지에 대한 이야기이다.


웹어플리케이션의 상태

우리가 만드는 웹어플리케이션은 하나의 큰 상태 머신이고 이를 구성하고 있는 크고 작은 단위들 또한 하나의 상태머신이다.
각각의 상태 머신들은 각자의 상태를 가지고 있고, 상태 머신들은 각자의 역할에 따라 서로 유기적으로 연결되어 있다.

A라는 작은 상태 머신의 상태값은 B의 입력값이 될수 있고, B의 상태값은 C와 D에 관련 있는 상태값 일수 있다.
따라서, A의 상태 값은 B로 전달되어야하고 B의 상태값은 다시 C와 D에 전달되어야만 한다.
이렇게 A의 상태 변화 정보가 B에 전달되어야하고 B의 상태값이 다시 C와 D에 전달되어야하는 이유는 바로 A, B, C, D 모듈간에 의존성이 있기 때문이다.

간단한 예를 들어보자.
사용자 정보(상태)를 System 클래스가 check() 함수에서 사용하고 있는 예제이다.
System과 User간에는 다음과 같은 의존성이 존재한다.

이렇게한 근본적인 이유는 데이터 흐름을 단순화함으로 복잡도를 낮추고 오류 발생 빈도를 줄이기 위해서 이다.
물론, 데이터가 양방향으로 흐르게 되면 사용상 편리할 수는 있다. 하지만, 어플리케이션의 규모가 커지게 되면 양방향으로 흐르는 데이터의 복잡도는 통제하기 어려울 정도로 복잡해진다.

이러한 이유로 최근 등장한 프레임워크들은 모두 단방향 데이터 흐름을 지향한다.

React와 Angular2+, Vue 모두 단방향 데이터 흐름을 지향한다.

Observable은 리액티브하다.

RxJS는 Observer 패턴과 마찬가지로 데이터가 발생하게되면 Observer에게 자동으로 그리고 빠르게 변경된 데이터를 전달한다.
이를 보고 리액티브하다고 이야기한다.
리액티브하다라는 의미를 이해하기 위해서는 우선 리액티브 프로그래밍(Reactive Programming)에 대한 정의 부터 살펴보자. 위키피디아에서는 다음과 같이 정의되어 있다.

리액티브 프로그래밍은 데이터 흐름과 상태 변화 전파에 중점을 둔 프로그램 패러다임이다. 사용되는 프로그래밍 언어에서 데이터 흐름을 쉽게 표현할 수 있어야하며 기본 실행 모델이 변경 사항을 데이터 흐름을 통해 자동으로 전파한다는 것을 의미한다.

출처 : https://en.wikipedia.org/wiki/Reactive_programming

위 정의에서 가장 핵심이 되는 단어는 데이터 흐름자동으로 전파이다. 즉, 상태 변화의 흐름이 자동으로 전파되는 것을 리액티브하다고 이야기한다.

리액티브의 가장 흔하게 드는 예로 ‘엑셀’을 이야기할 수 있다.

A열의 값과 B열의 값의 합을 나타내는 C열은 A열이나 B열의 값이 변화되는 경우 자동으로 C열의 값이 변경된다.
이렇게 A나 B열의 변경사항이 데이터 흐름을 통해 자동으로 C열에 전파되도록 구조화하는 프로그래밍의 패러다임을 리액티브 프로그래밍이라고 한다.

이 말은 앞에서 살펴 보았던 상태 변화에 대한 우리의 고민들과 일맥 상통한다고 이야기할 수 있다.
이런 고민의 해결책이 Observer 패턴이고, RxJS는 이런 Observer 패턴을 개선하여 어플리케이션에서 발생하는 모든 데이터를 리액티브하게 전달 할 수 있게 해준다. 따라서, RxJS는 리액티브 프로그래밍(Reactive Programming)을 지향하는 라이브러리이다.

정리

이 장에서는 웹어플리케이션의 상태가 어떻게 전파되는지를 살펴 봄으로써 상태 전파로 인해 발생할 수 있는 문제점을 살펴보았다. 또한, 이러한 문제를 효과적으로 해결했던 Observer 패턴에 대해서도 살펴보았다.
Observer 패턴은 느슨하게 연결되어 Subject와 Observer간의 의존도를 줄였으며, Push 방식으로 데이터를 전파함으로써 상태 전파에 대한 많은 문제를 해결했다.
RxJS에서는 이런 Observer 패턴을 개선하여 상태 전파 문제를 해결하려고 하였다.
에러상황과 종료상황에 대한 인터페이스를 확장하였고, 데이터를 단방향으로 흐를 수 있도록 개선함으로써 코드의 복잡도를 낮추었다. 이런 결과 RxJS는 궁극적으로 Reactive Programming을 지향하는 라이브러리가 되었다.

다음장에서는 RxJS가 고민한 로직 오류에 대해 살펴보기로 하자.

Comment and share

몇일 전에 요즘 내가 쓰고 있는 책의 초안을 일부를 공개하기로 셀프 선언한 이후, 사실 너무 바빴다 ㅠㅠ
1부 탈고일이 좀 남았지만… 마음이 급하다.
지인과는 과감히 탕수육 내기도 했으니. 더 급하다.

처음이니깐 간단히 책소개만 해보면.

RxJS가 무엇을 위해 준비된 라이브러리인지에 대한 답을 구하는 책이다. 더불어 RxJS의 활용법도 학습하는 책이다. 참고로 rxjs5 기준으로 쓰고 있다

오늘 소개할 부분은
웹어플리케이션 개발시 발생할 수 있는 입력 오류를 RxJS는 어떻게 접근했는지에 대한 이야기이다.


웹어플리케이션의 입력 데이터

웹어플리케이션의 동작 과정을 되돌아보면 사실 몇 개의 큰 과정으로 나눌 수 있다.

간단한 게시판을 예로 생각해보자.
게시판은 서버에 저장된 글을 보여주는 목록화면과 게시글의 내용을 보여주는 상세화면으로 구성되어 있다.
서버로부터 저장된 글에 대한 정보를 받고, 받은 정보를 바탕으로 화면과 관련된 UI작업을 한다.
게시글의 종류나 카테고리를 셀렉트 박스로 표현할 수도 있고, 작성된 글의 내용 일부를 화면에 표현하기도 한다.

또한 사용자가 게시글을 등록. 수정하는 편집화면도 있다. 편집화면에서는 사용자가 셀렉트 박스를 선택하기도 하고, 글을 입력하기도 한다. 사용자의 입력이 잘못된 경우에는 사용자에게 메시지를 전달하기도 하기도 한다.
글의 작성 및 수정이 끝나면 등록한 정보를 서버에 저장한다.

이 과정을 데이터가 흐르는 관점으로 살펴보면

    1. 목록화면과 조회화면은 서버로부터 데이터를 불러와 브라우저에게 전달한다.
    1. 브라우저에 전달된 정보를 브라우저의 UI객체에 전달한다.
    1. 편집화면은 브라우저 UI객체를 통해 사용자 입력정보를 전달받고 이를 다시 브라우저의 다른 UI객체나 브라우저 객체에 전달한다.
    1. 사용자가 작성한 정보를 브라우저 UI객체나 브라우저 객체를 이용하여 서버로 전달한다.

이 과정을 다시 상태머신 관점에서 살펴보자.
1)과 2)의 과정에서 입력값은 서버로부터 전달 받은 게시글 데이터가 된다. 두 과정의 입력값이 동일 할지라도 입력값을 받는 브라우저와 브라우저 UI 객체는 서로 다른 시점에 입력값을 전달받는다.
예를 들어 1)과정이 Ajax로 JSON 데이터를 받아와 브라우저의 객체로 저장하는 경우라면 Ajax는 비동기(Asynchronous) 호출로 데이터를 받기 까지 시간이 걸린다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// XMLHttpRequest에 의해 입력된 데이터
let result;
const xhr = new XMLHttpRequest();
xhr.onload = function(e) {
/*
* Ajax를 통해 얻은 데이터를 result 변수에 저장한다.
*
* {
* list: [
* "게시글1번. 안녕하세요.",
* "게시글2번. 반갑습니다.",
* "게시글3번. RxJS에 대해 알아봐요."
* ]
* }
*/
result = JSON.parse(xhr.responseText);
};
xhr.open("GET", url);
xhr.send();

반면, 브라우저 UI객체는 이미 브라우저에 존재하는 JSON 데이터를 받기 때문에 동기(Synchronous) 호출로 바로 결과 데이터를 얻을수 있다.

1
2
3
4
5
<ul>
<li></li>
<li></li>
<li></li>
</ul>
1
2
3
4
Array.from(document.querySelectorAll("li")).forEach((v, i) => {
// Ajax의 결과인 result 변수를 이용하여 DOM에 정보를 표현한다.
v.innerText = result.list[i];
});

마찬가지로 3)과 4)의 과정도 입력값은 사용자의 입력이 되지만, 전달된 입력값을 처리하는 시점은 상황에 따라 각각 다르다.

앞에서 설명한 내용을 간단히 정리하면 다음과 같다.

과정 데이터 데이터 흐름 전달 시점 예제
1) 게시글 서버 -> 브라우저 비동기 Ajax 통신으로 JSON 데이터를 받는다.
2) 게시글 브라우저 -> 브라우저 UI 객체 동기 JSON 데이터를 UI에 반영한다.
3) 사용자가 입력한 내용 사용자 -> 브라우저 UI객체 비동기 <textarea>를 통해 사용자 입력을 받는다.
4) 사용자가 입력한 내용 브라우저 UI 객체 -> 브라우저 -> 서버 동기, 비동기 <textarea>에 있는 데이터를 JSON 객체로 저장 후, 서버로 Ajax 요청을 한다

입력 데이터의 전달 시점이 다양하다.

앞에서 살펴본 바와 같이 입력데이터가 같을지라도 실제 각 객체들 사이로 데이터가 전달되는 시점은 다르다. 어떤 상황에서는 동기(Synchronous) 방식으로 데이터를 주고 받고, 어떤 상황에서는 비동기(Asynchronous) 방식으로 데이터를 주고 받는다.
이런 구조는 비단 웹어플리케이션 뿐만이 아니다. 소프트웨어 전반적으로 이와 같은 상황이 발생한다.
이런 이유는 두 방식의 차이점을 살펴보면 보다 명확히 알수 있다.

동기(Synchronous)

동기방식은 작업이 들어온 순서에 맞게 차근차근 하나씩 진행되는 것을 의미한다. 호출하는 함수가 호출되는 함수의 작업 완료를 기다린 후 그 다음을 진행하는 방식이다. 이 방식의 장점은 순차적으로 진행되기 때문에 개발이 쉽다. 반면, 처리하는 작업이 많을 경우에는 전체 작업 속도가 느려진다. 특히, 웹브라우저와 같이 단일 UI쓰레드를 사용하는 경우에는 해당 작업이 끝날때까지 브라우저는 대기하고 있어야만 한다.

이벤트가 아닌 동기(Synchronous) 방식인 함수호출도 시간이라는 개념을 도입하면 다음과 같이 표현될 수 있다.

결국 동기와 비동기는 시간의 축으로 봤을때는 같은 형태인 것이다.
또한, 이런 형태는 시간을 인덱스로 둔 컬렉션으로 생각할 수도 있다. RxJS에서는 이를 스트림(Stream)이라 표현한다.

RxJS에서는 이런 Stream을 표현하는 Observable 클래스를 제공한다.

Observable

Observable은 시간을 인덱스로 둔 컬렉션을 추상화한 클래스이다.
이 클래스는 동기나 비동기의 동작 방식으로 전달된 데이터를 하나의 컬렉션으로 바라볼 수 있게 해준다.
이렇게 함으로써 개발자는 데이터가 어떤 형태로 전달되는지에 대해 더이상 고민할 필요가 없어진다.
단지, Observable을 통해 데이터를 전달 받기만 하면 된다.

Observable의 표준화

RxJS의 Observable은 Rx에서 만든 라이브러리이기도 하지만, ECMAScript에 표준으로 제안된 스펙이기도하다.
https://github.com/tc39/proposal-observable

이 책에서 다루는 RxJS5는 ECMAScript에 제안된 표준 스펙을 기반으로 작성된 라이브러리이다.

모든 데이터는 Observable로 만들 수 있다.

Observble은 모든 데이터를 다룬다.

  • 키보드를 눌러서 입력된 데이터
  • 마우스를 이동하거나 클릭해서 입력된 데이터
  • Ajax/fetch 요청을 통해 얻은 데이터
  • Web socket을 통해 전달된 데이터
  • Message를 통해 전달된 데이터

Comment and share

  • page 1 of 1
Author's picture

sculove

아내와 아들 그리고 딸밖에 모르는 남편


FrontEnd Developer


대한민국/서울