(by @andrestaltz)
(orginal source: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)
๋ง์ฝ ์ค์ ์ฝ๋ฉํ๋ ๋น๋์ค ํํ ๋ฆฌ์ผ์ ๋ณด๊ณ ์ถ๋ค๋ฉด, ์ด ๊ธ๊ณผ ๊ฐ์ ๋ด์ฉ์ผ๋ก ๋ นํํ ๋น๋์ค ์๋ฆฌ์ฆ๋ฅผ ํ์ธํ์ธ์: Egghead.io - Introduction to Reactive Programming.
์๋ง ๋น์ ์ Reactive Programming, ํนํ ์ด๊ฒ์ ๋ค์ํ ํํ์ธ Rx, Bacon.js, RAC ๋ฑ๋ฑ์ ๋ฐฐ์ฐ๋ ๊ฒ์ ๋ํด์ ๊ดํด์ ๊ด์ฌ์๋ ์ฌ๋์ผ๊ฒ๋๋ค.
Reactive Programming์ ์ด๋ ต์ต๋๋ค. ์ฌ์ง์ด ์ข์ ํ์ต์๋ฃ๊ฐ ๋ณ๋ก ์๊ธฐ ๋๋ฌธ์ ๋ฐฐ์ฐ๊ธฐ๊ฐ ๋ ์ด๋ ต์ต๋๋ค. ์ ๊ฐ Reactive Programming์ ์ฒ์ ์์ํ์๋, ํํ ๋ฆฌ์ผ์ ์ฐพ์๋ณด๋ ค ํ์ง๋ง, ์์์ ๊ฐ์ด๋ ๋ฌธ์๊ฐ ์ ๋ถ์์ต๋๋ค. ๊ฒ๋ค๊ฐ ๊ทธ๊ฒ๋ค์ ์๋ฐ ๊ฒํฅ๊ธฐ์ ๋ด์ฉ์ด์๊ณ , Reactive Programming ์ผ๋ก ์ํคํ ์ณ๋ฅผ ์๋ ์ด๋ ค์ด ๋ถ๋ถ์ ์ ํ ๋ค๋ฃจ์ง ์์์ง์. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฌธ์๋ ์ด๋ค ํจ์๋ฅผ ์ดํดํ๋ ค๊ณ ํ ๋ ๊ฑฐ์ ๋์์ด ๋์ง ์์์ต๋๋ค. ์ง์ง์์. ์ด๊ฑธ ๋ณด์ธ์:
Rx.Observable.prototype.flatMapLatest(selector, [thisArg])
Observable sequence์ ์๋ ๊ฐ ์์๋ฅผ ์์์ index์ ์ฐ๊ณํ์ฌ ์๋ก์ด Observable sequence์ sequence๋ก project์ํต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋์, observable sequence๋ค์ observable sequence๋ฅผ ๊ฐ์ฅ ์ต๊ทผ observable sequence์ผ๋ก๋ถํฐ ๊ฐ์ ์์ฐํด๋ด๋ observable sequence๋ก ๋ฐ๊ฟ๋๋ค.
Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
์ ๊ธฐ๋...
์ ์ฑ ์ ๋๊ถ ์ฝ์๋๋ฐ์, ํ๋๋ ๊ทธ๋ฅ ํฐ๊ทธ๋ฆผ์ ๊ทธ๋ ค์คฌ๊ณ , ๋ค๋ฅธ ํ๋๋ Reactive library ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ง ์๋ ค์คฌ์ต๋๋ค. ์ด์ฏค์์ ์ Reactive Programming ์ ์ด๋ฐ ๋ฐฉ๋ฒ์ผ๋ก ๋ฐฐ์ฐ๋๊ฑธ ํฌ๊ธฐํ๊ณ , ์ง์ ๋ง๋ค์ด๋ณด๋ฉด์ ์์๋ณด๊ธฐ๋ก ํ์ต๋๋ค. Futurice ์์ ์ผํ ๋, ์ ๋ Reactive Programming ์ ์ค์ ํ๋ก์ ํธ์ ์ฌ์ฉํด๋ณด๊ธฐ ์์ํ๊ณ , ๋ฌธ์ ๊ฐ ๋ถ๋ชํ์ ๋ ๋ช๋ช ๋๋ฃ๋ค์ ๋์์ ์ป์ ์ ์์์ต๋๋ค.
Reactive Programming์ ๋ฐฐ์ธ๋ ๊ฐ์ฅ ์ด๋ ค์ ๋ ๋ถ๋ถ์, Reactive์ฒ๋ผ ์๊ฐํ๊ธฐ ์์ต๋๋ค. ๊ทธ๊ฒ์, ๋ช ๋ น(imperative)๊ณผ ์ํ(stateful)๋ฅผ ์์ ํ๋ ์ ํ์ ์ธ ํ๋ก๊ทธ๋๋ฐ๋ฐฉ์์ ๋ฒ๋ฆฌ๊ณ , ์๋ก์ด ํจ๋ฌ๋ค์์ผ๋ก ์ฌ๊ณ ๋ฅผ ์ ํํ๋ ๊ฒ์ด์์ต๋๋ค. ์ ๋ ์ด๋ฐ ๊ด์ ์ผ๋ก ์ฐ์ธ ๊ฐ์ด๋๋ฅผ ์ธํฐ๋ท์์ ์ฐพ์ง ๋ชปํ๊ณ , ์ฒ์ ์์ํ๋ ์ฌ๋๋ค์ ์ํด Reactive๋ก ์๊ฐํ๋ ๋ฐฉ๋ฒ์ ์๋ ค์ฃผ๋ ํ์ค์ ์ธ ํํ ๋ฆฌ์ผ์ด ํ์ํ๋ค๊ณ ๋ณด์์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฌธ์๋ ๊ทธ ์ดํ์ ๋์์ด ๋ ๊ฒ๋๋ค. ์ด ๊ฐ์ด๋๊ฐ ๋น์ ์๊ฒ ๋์์ด ๋๊ธธ ๋ฐ๋๋๋ค.
์ธํฐ๋ท์๋ Reactive Programming์ ๋ํ ๋์ ์ค๋ช ๊ณผ ์ ์๋ค์ด ๋๋ ค์์ต๋๋ค. Wikipedia ๋ ๋ ๊ทธ๋ ๋ฏ์ด ๋๋ฌด ์ผ๋ฐ์ ์ด๊ณ ์ด๋ก ์ ์ ๋๋ค. Stackoverflow์ ์๋ ํ์ค ๋ต๋ณ์, ์ด์ฌ์๋คํํ ๊ฒฐ์ฝ ์ ์ ํ์ง ์์ต๋๋ค. Reactive Manifesto ๋ ๋ง์น ํ์ฌ์์ ํ๋ก์ ํธ ๋งค๋์ ๋ ๊ฑฐ๋์ฒ ์ฌ๋ํํ ์๊ฐํ๋ ๊ฒ์ฒ๋ผ ๋ค๋ฆฝ๋๋ค. Microsoft์ Rx terminology "Rx = Observables + LINQ + Schedulers" ์ ์ค๋ช ์ด ๋๋ฌด ๋ฌด๊ฒ๊ณ , ์ฐ๋ฆฌ ๋๋ถ๋ถ์ ํผ๋์ค๋ฝ๊ฒํ๋ Microsoftishํ ์คํ์ผ ์ ๋๋ค. "reactive"์ "propagation of change" ๋ผ๋ ์ฉ์ด๋ ์ ํ์ ์ธ MV* ์ ์ธ๊ธฐ์๋ ์ธ์ด์์ ์ด๋ฏธ ์ฐ๊ณ ์๋ ๊ฐ๋ ์ด๋ผ์ ์ ํ ์๋กญ๊ฒ ๋๊ปด์ง์ง ์์ต๋๋ค. ์ ๋ฌผ๋ก , ์ ํ๋ ์์์ view๋ค์ ๋ชจ๋ธ๋ค๋ก๋ถํฐ ๋ฐ์(react)ํ๊ณ ์์ต๋๋ค. ๋ณํ๊ฐ ์ ํ๋๋ค๋ ๊ฒ์(change is propagated) ๋น์ฐํฉ๋๋ค. ๋ง์ฝ ๊ทธ๊ฒ ์๋๋ค๋ฉด, ์๋ฌด๊ฒ๋ ๋ ๋๋์ง ์๊ฒ ์ฃ .
ํ์๋ฆฌ๋ ์ฌ๊ธฐ๊น์ง๋ก ๋๋ ์๋ค.
์ด๋ค ๋ฉด์์ ์ด๊ฒ์ ์ ํ ์๋กญ์ง ์์ต๋๋ค. ์ด๋ฒคํธ ๋ฒ์ค๋, ์ ํ์ ์ธ ํด๋ฆญ ์ด๋ฒคํธ๋ ๋น์ฐํ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ค์ ๋๋ค. ๋น์ ์ ์ด๊ฒ๋ค์ observeํ ์ ์๊ณ , side effect๋ฅผ ์ค ์ ์์ต๋๋ค. Reactive๋ ์ด ์์ด๋์ด๋ฅผ ํ์ฅ์ํจ๊ฒ๋๋ค. ๋น์ ์ ํด๋ฆญ์ด๋ hover ์ด๋ฒคํธ ์ธ์, ์ด๋ค๊ฒ์ผ๋ก๋ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ๋ง๋ค ์ ์์ต๋๋ค. ์คํธ๋ฆผ์ ๊ฐ๋ณ๊ณ , ํํฉ๋๋ค. variables, user inputs, properties, caches, data structures, ๊ธฐํ ๋ฑ๋ฑ, ์ด๋ค๊ฒ์ด๋ ์คํธ๋ฆผ์ด ๋ ์ ์์ต๋๋ค. ์๋ฅผ๋ค์ด, ํธ์ํฐ ํผ๋๋ฅผ ํด๋ฆญ ์ด๋ฒคํธ์ ๊ฐ์ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ด๋ผ๊ณ ์๊ฐํด ๋ณผ ์ ์์ต๋๋ค. ๋น์ ์ ๊ทธ ์คํธ๋ฆผ์ listen ํ ์ ์๊ณ , ์ ์ ํ๊ฒ react ํ ์ ์์ต๋๋ค.
๊ทธ์ ๋ํ์ฌ, ๋น์ ์ ์คํธ๋ฆผ๋ค์ ์กฐํฉํ๊ณ , ๋ง๋ค๊ณ , ํํฐ๋งํ ์ ์๋ ์ด๋ง์ด๋งํ ํจ์ ๊พธ๋ฌ๋ฏธ๋ค์ ์ป๊ฒ ๋ฉ๋๋ค. ์ด๊ฑด "ํจ์ํ(functional)"์ด๋ผ๋ ๋ง๋ฒ์ด ๋น์ ๋ฐํ๋ ์๊ฐ์ ๋๋ค. ์คํธ๋ฆผ์ ๋ค๋ฅธ ์คํธ๋ฆผ์ input์ด ๋ ์ ์์ต๋๋ค. ๋ณต์์ ์คํธ๋ฆผ๋ค๋ ๋ค๋ฅธ ์คํธ๋ฆผ์ input์ด ๋ ์ ์์ต๋๋ค. ๋ ๊ฐ์ ์คํธ๋ฆผ์ ํฉ์น ์๋ ์์ต๋๋ค. ์คํธ๋ฆผ์ ํํฐ๋ง ์์ผ์, ๋น์ ์ด ๊ด์ฌ์๋ ์ด๋ฒคํธ๋ค๋ง ์๋ ์คํธ๋ฆผ์ผ๋ก ๋ง๋ค ์๋ ์์ต๋๋ค. ์คํธ๋ฆผ์ ์๋ ๋ฐ์ดํฐ ๊ฐ๋ค์ ์๋ก์ด ์คํธ๋ฆผ์ผ๋ก map ์ํฌ ์๋ ์์ต๋๋ค.
์คํธ๋ฆผ์ด Reactive์์ ๊ทธ๋ ๊ฒ ์ค์ํ ๊ฒ์ด๋ผ๋ฉด, ์ข๋ ์์ธํ๊ฒ ์ดํด๋ณด๋๋ก ํฉ์๋ค. ์ด๋ฏธ ๋ค๋ค ์ตํ ์๊ณ ์์ "๋ฒํผ ํด๋ฆญ" ์ด๋ฒคํธ ์คํธ๋ฆผ๋ถํฐ ์์ํด๋ด ์๋ค.
์คํธ๋ฆผ์ ์๊ฐ ์์ผ๋ก ์ ๋ ฌ๋ ์งํ์ค์ธ ์ด๋ฒคํธ ๋ค์ ๋์ด ์ ๋๋ค. ์คํธ๋ฆผ์ (ํ์ ์ ๊ฐ๊ณ ์๋) ๊ฐ, ์๋ฌ, ์๋ฃ ์ ํธ, ์ด ์ธ๊ฐ์ง ์ข ๋ฅ์ ์ด๋ฒคํธ๋ฅผ ๋ฐ์(emit)์ํฌ ์ ์์ต๋๋ค. "์๋ฃ"๋ ์ฐฝ์ด๋ ์ด ๋ฒํผ์ ํฌํจํ๊ณ ์๋ ํ๋ฉด์ด ๋ซํ์ง๋ ์๊ฐ์ ์๋ฏธํ๋ค๊ณ ๋ด ์๋ค.
์ฐ๋ฆฌ๋ ๊ฐ ์ด๋ฒคํธ(๊ฐ, ์๋ฌ, ์๋ฃ)๊ฐ ๋ฐ์ํ ๋ ์คํ๋์ด์ง ํจ์๋ค์ ๊ฐ๊ฐ ์ ์ํจ์ผ๋ก์จ, ์ด ๋ฐ์ํ ์ด๋ฒคํธ๋ค์ ๋น๋๊ธฐ์ ์ผ๋ก๋ง ํฌ์ฐฉํ ์ ์์ต๋๋ค. ์ข ์ข , ์๋ฌ์ ์๋ฃ ์ด๋ฒคํธ๋ ์๋ตํ๊ณ , ๊ฐ์ ๋ด๋ณด๋ด๋ ์ด๋ฒคํธ๋ง ํฌ์ฐฉํ๋ ํจ์๋ฅผ ์ ์ํ ๋๊ฐ ๋ง์ต๋๋ค. ์คํธ๋ฆผ์ "listening"ํ๋ ๊ฒ์, subscribing(๊ตฌ๋ ) ์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ ์ํ ํจ์๋ค์ observer๋ค์ ๋๋ค. ์คํธ๋ฆผ์ observed ๋๋ ๋์(subject)์ ๋๋ค("observable"์ด๋ผ๊ณ ๋ ํ์ฃ ) ์ด๊ฒ์ Observer Design Pattern ๊ณผ ์ ํํ ์ผ์นํฉ๋๋ค.
์ ๋ํ๋ ASCII๋ก๋ ๊ทธ๋ฆด ์ ์์ต๋๋ค. ์ด ํํ ๋ฆฌ์ผ์์๋ ์ผ๋ถ ์ฌ์ฉํ ๊ฒ๋๋ค.
--a---b-c---d---X---|->
a, b, c, d are emitted values
X is an error
| is the 'completed' signal
---> is the timeline
๋๋ฌด ์ต์ํ ๊ฒ๋ค์ด๊ธฐ ๋๋ฌธ์, ๋ ์ง๋ฃจํ๊ฒ ๋ง๋ค๊ณ ์ถ์ง ์๋ค์. ์ด๋ฒ์ ์๋ก์ด๊ฑธ ํด๋ด ์๋ค. ๊ธฐ์กด์ ํด๋ฆญ ์ด๋ฒคํธ ์คํธ๋ฆผ์ ๋ณํ์์ผ์, ์๋ก์ด ํด๋ฆญ ์ด๋ฒคํธ ์คํธ๋ฆผ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ , ๋ฒํผ์ด ๋ช๋ฒ์ด๋ ํด๋ฆญ๋์๋์ง๋ฅผ ์๋ฏธํ๋ ๊ณ์(counter) ์คํธ๋ฆผ์ ๋ง๋ค์ด๋ด
์๋ค. ๋ณดํต Reactive library์๋, ์คํธ๋ฆผ์ ๋ถ์ผ ์ ์๋ ๋ง์ ํจ์ ๋ชฉ๋ก(map, filter, scan)์ ๊ฐ๊ณ ์์ต๋๋ค. clickStream.map(f)
์ฒ๋ผ ๊ทธ ํจ์๋ค ์ค ํ๋๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด, ํด๋ฆญ ์คํธ๋ฆผ์ ๊ธฐ์ดํด์ ์๋ก์ด ์คํธ๋ฆผ ์ ๋๋๋ ค์ค๋๋ค. ์ด๊ฒ์ ๊ธฐ์กด์ ํด๋ฆญ ์คํธ๋ฆผ์ ์ ํ ๋ณํํ์ง ์์ต๋๋ค. ์ด๋ฐ ์์ฑ์ ๋ถ๋ณ์ฑ(immutability) ์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ, ๋ง๊ฑธ๋ฆฌ์ ํ์ ์ด ์ฐฐ๋ก๊ถํฉ์ธ ๊ฒ์ฒ๋ผ Reactive ์คํธ๋ฆผ๊ณผ ์
์ด์ธ๋ฆฌ๋ ์์ฑ์
๋๋ค. ์ด ๋๋ถ์ ์ฐ๋ฆฌ๋ clickStream.map(f).scan(g)
์ฒ๋ผ ํจ์ ์ฒด์ด๋๋ ํ ์ ์๊ฒ ๋ฉ๋๋ค.
clickStream: ---c----c--c----c------c-->
vvvvv map(c becomes 1) vvvv
---1----1--1----1------1-->
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->
map(f)
ํจ์๋ ๊ฐ๊ฐ ๋ฐ์(emit)ํ๋ ๊ฐ๋ค์, ๋น์ ์ด ์ ์ํ ํจ์ f
์ ํต๊ณผ์ํจ ํ, ์ด ์๋ก์ด ๊ฐ๋ค์ ๊ฐ์ง๋ ์๋ก์ด ์คํธ๋ฆผ์ ๋ง๋ค์ด๋
๋๋ค. ์ฐ๋ฆฌ ์ํฉ์ ์๋ก ๋ค์ด๋ณด์๋ฉด, ์ฐ๋ฆฌ๋ ๊ฐ๊ฐ์ ํด๋ฆญ์ 1๋ก map์์ผฐ์ต๋๋ค. scan(g)
ํจ์๋, ์คํธ๋ฆผ์์ ์ด์ ๊ฐ๋ค์ ๋ชจ๋ ํฉ์ณ์, x = g(accumulated, current)
๋ผ๋ ์๋ก์ด ๊ฐ์ ๋ง๋ค์ด๋
๋๋ค. g
๋ ์ด ์์ ์์ ์ ์ํ ๋จ์ํ ๋ํ๋ ํจ์๋ฅผ ์๋ฏธํฉ๋๋ค. ์ดํ, counterStream
์ ํด๋ฆญ์ด ๋ฐ์ํ ๋๋ง๋ค ์ด ํด๋ฆญ ํ์๋ฅผ ๋ฐ์์ํต๋๋ค.
Reactive์ ์ง์ง ํ์ ๋ณด๊ธฐ ์ํด, ์ด๋ฒ์ ๋น์ ์ด "๋๋ธ ํด๋ฆญ" ์ด๋ฒคํธ ์คํธ๋ฆผ์ ๊ฐ๊ธธ ์ํ๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค. ๋ ์ฌ๋ฐ๊ฒ ํ๊ธฐ ์ํด์, ์ฐ๋ฆฌ๋ ์ ์คํธ๋ฆผ์ด ๋๋ธํด๋ฆญ์ ๋ฐ๋ฏ์ด ์ผ์คํด๋ฆญ๋ ๋ฐ๋๋ก ์ทจ๊ธํ๋๊ฐ, ์๋๋ฉด ์์ ์ผ๋ฐํํด์, ๋ค์ค ํด๋ฆญ์ ๋ฐ๋๋ก ํ๊ณ ์ถ๋ค๊ณ ์๊ฐํด๋ณด์ฃ . ์, ์จ ํ๋ฒ ํฌ๊ฒ ๋ค์ด์ฌ๊ณ , ์ ํต์ ์ธ ๋ช ๋ น(imperative)์ ์ํ(stateful)๋ฅผ ์์ ํ๋ ๋ฐฉ์์ผ๋ก, ์ด๊ฑธ ์ด๋ป๊ฒ ํด์ผํ ์ง ์์ํด๋ณด์ธ์. ์ฅ๋ด์ปจ๋ฐ ์์ฃผ ํํธ ์๋ ๋ฐฉ๋ฒ์ผ๊ฒ๋๋ค. ์ํ๋ฅผ ์ ์งํ๋ ๋ณ์๋ฅผ ๋ง๋ค๊ณ , ์๊ฐ ๊ฐ๊ฒฉ ์กฐ์์ ์ข ํด์ฃผ๊ฒ ์ฃ .
ํ, Reactive์์ ๊ฐ๋จํ๋ค๊ตฌ์. ์ฌ์ค, ๊ทธ ๋ก์ง์ 4์ค์ง๋ฆฌ ์ฝ๋ ์ ๋๋ค. ํ์ง๋ง, ์ฝ๋๋ ์ ๊น ์๋๋ก ํฉ์๋ค. ์ด์ฌ์๊ฑด ์ ๋ฌธ๊ฐ๊ฑด, ์คํธ๋ฆผ์ ์ดํดํ๊ณ ๋ง๋๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์, ๋ํ(๋ค์ด์ด๊ทธ๋จ, ๊ทธ๋ฆผ)๋ฅผ ์๊ฐํ๋๊ฒ๋๋ค.
ํ์ ์์๋ ํ ์คํธ๋ฆผ์ ๋ค๋ฅธ ์คํธ๋ฆผ์ผ๋ก ๋ฐ๊พธ๋ ํจ์๋ฅผ ์๋ฏธํฉ๋๋ค. ์ฒซ์งธ๋ก,buffer(clickStream.throttle(250ms))
์, 250ms์ "์ด๋ฒคํธ ์นจ๋ฌต"์ด ๋ฐ์ํ๋ฉด ํด๋ฆญ์ ๋ฆฌ์คํธ์ ๋์ ์ํต๋๋ค. ๋น์ฅ ๋๋ฌด ์์ธํ๊ฒ ๋ค ์ ํ์๋ ์์ต๋๋ค. ์ง๊ธ์ ๊ทธ์ Reactive์ ์์ฐ์ ํด๋ณด๊ณ ์๋ ๊ฑฐ๋๊น์. ๊ทธ ๊ฒฐ๊ณผ๋, ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ง ์คํธ๋ฆผ์
๋๋ค. ์ฐ๋ฆฌ๋ ์ด ์คํธ๋ฆผ์ map()
์ ์ ์ฉ์์ผ์, ๊ฐ๊ฐ์ ๋ฆฌ์คํธ๋ฅผ ๊ทธ ๋ฆฌ์คํธ์ ๊ธธ์ด๋ฅผ ๊ฐ์ง ์ ์ ๊ฐ์ผ๋ก mapping ์ํฌ๊ฒ๋๋ค. ๋ง์ง๋ง์ผ๋ก, ์ฐ๋ฆฌ๋ filter(x >= 2)
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ์ ์ ์ 1
์ ๋ฌด์ํ๋๋ก ํ๊ฒ ์ต๋๋ค. ์ด๊ฒ ๋ค์
๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ํ๋ ์คํธ๋ฆผ์ ๋ง๋ค๊ธฐ ์ํด์ ์ธ ๋จ๊ณ์ ์์
์ด ์ ๋ถ์์ต๋๋ค. ๊ทธ๋ฌ๊ณ ๋๋ฉด ๋ง์ง๋ง ์คํธ๋ฆผ์ subscribe๋ฅผ ํจ์ผ๋ก์จ(์คํธ๋ฆผ์ listenํ๋๊ฒ๋๋ค), ์ฐ๋ฆฌ๊ฐ ์ํ๋๋๋ก ๋ฐ์ํ๊ฒ ๋ง๋ค๋ฉด ๋ฉ๋๋ค.
๋ถ๋ ๋น์ ์ด ์ด๋ฐ ์ ๊ทผ์ ์๋ฆ๋ค์์ ์ฆ๊ฒผ์ผ๋ฉด ํ๋ค์. ์ด ์์ ๋ ๋น์ฐ์ ์ผ๊ฐ์ ๋๋ค. ๋น์ ์ ๋๊ฐ์ ์์ ์ ์๋ก ๋ค๋ฅธ ์ข ๋ฅ์ ์คํธ๋ฆผ์ ์ ์ฉ์ํฌ ์ ์์ต๋๋ค. ์๋ฅผ๋ค์๋ฉด, API ์๋ต ์คํธ๋ฆผ ๋ง์ด์ง์. ๋ ํํธ์ผ๋ก , ๊ทธ ์ธ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ง์ ํจ์๋ค๋ ์์ต๋๋ค.
Reactive Programming์ ์ฝ๋์ ์ถ์ํ ๋ ๋ฒจ์ ๋์ด์ฌ๋ ค์ค์ผ๋ก์จ, ๋น์ ์ผ๋ก ํ์ฌ๊ธ ๋น์ฆ๋์ค ๋ก์ง์ ๊ท์ ํ๋ ์ด๋ฒคํธ๋ค ๊ฐ์ ์ํธ ๊ด๊ณ์๋ง ์ง์คํ ์ ์๊ฒ ํด์ค๋๋ค. ๋์ด์ ์ธ๋ถ ๊ตฌํ์ ์ด๋ป๊ฒ ํ๋ ๊ณ์ ๋ง์ง์๊ฑฐ๋ฆฌ๊ณ ์์ ํ์๊ฐ ์๊ฒ ๋ฉ๋๋ค. Reactive Programming ์ผ๋ก ์ง ์ฝ๋๋ ๋งค์ฐ ๊ฐ๊ฒฐํฉ๋๋ค.
์ด๋ฌํ ์ฅ์ ์, ๋ฐ์ดํฐ ์ด๋ฒคํธ๋ค์ด ๋ง์ UI ์ด๋ฒคํธ๋ค๊ณผ ์ฐ๊ณํ์ฌ ์ํธ์์ฉํ๊ณ ์๋ ์ต๊ทผ์ ์น์ฑ์ด๋ ๋ชจ๋ฐ์ผ์ฑ๋ค์๊ฒ ๋๋ ทํ๊ฒ ๋๋๋ผ์ง๋๋ค. 10๋ ์ ์๋, ์น ํ์ด์ง๋ค์ ์ํธ์์ฉ์ด ๋จ์ํ๊ฒ ๊ธด ํผ์ ๋ฐฑ์๋์ ๋ณด๋ด๊ณ ํ๋ก ํธ์๋์์ ๋จ์ํ ๋ ๋๋ง์ ์ํํ๋ ๊ฒ ์ ๋ถ์์ต๋๋ค. ํ์ง๋ง ์ง๊ธ์ ์ฑ๋ค์ ๋์ฑ ์งํํ์ฌ ์ค์๊ฐ ์์ ์ ์ํํ๊ณ ์์ต๋๋ค. ํ ํผ ํ๋์ ๊ฐ์ ์์ ํ๋ฉด ๋ฐฑ์๋์์ ์๋์ผ๋ก ์ ์ฅ์ด ๋์ด๋ฒ๋ฆฌ์ฃ . ์ด๋ค ์ปจํ ์ธ ์ "์ข์์"๋ฅผ ๋๋ฅด๋ฉด, ๋ค๋ฅธ ์ฐ๊ฒฐ๋์ด ์๋ ์ ์ ์๊ฒ ์ค์๊ฐ์ผ๋ก ๋ฐ์๋ ์ ์์ต๋๋ค.
์ง๊ธ์ ์ฑ๋ค์ ๋์ ์์ค์ผ๋ก ์ฌ์ฉ์์๊ฒ ์ํธ์์ฉ ๊ฒฝํ์ ์ ๊ณตํ๋ ๊ฑฐ์ ๋ชจ๋ ์ข ๋ฅ์ ์ค์๊ฐ ์ด๋ฒคํธ๋ค์ด ๋์ณ๋๊ณ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ด๋ฌํ ๊ฒ๋ค์ ์ ์ ํ๊ฒ ๋ค๋ฃฐ ๋๊ตฌ๊ฐ ํ์ํ๋ฉฐ, Reactive Programming์ ๋ฐ๋ก ๊ทธ ํด๋ต์ ๋๋ค.
์ด๋ฒ์ ์ค์ ์ ์ธ ๊ฒ๋ค์ ๋ค๋ค๋ณด๋๋ก ํฉ์๋ค. Reactive Programming ๋ฐฉ์์ผ๋ก ์๊ฐํ๊ธฐ ์ํ ํ์ค ์ธ๊ณ ์์์ ๋๋ค. ์ต์ง๋ก ๋ง๋ ์์๋ ์๋๊ณ , ๋ฐ์ฏค ์ค๋ช ๋ ์ปจ์ ๋ ์๋๋๋ค. ์ด ํํ ๋ฆฌ์ผ์ด ๋๋ ๋์ฏค์ด๋ฉด, ์ฐ๋ฆฌ๊ฐ ๊ฐ๊ฐ์ ๊ฒ๋ค์ ์ ํ๋์ง ์ดํดํ ์ ์๊ฒ ๋จ๊ณผ ๋์์, ์ง์ง ํจ์ํ ์ฝ๋๋ฅผ ์งค ์ ์์ ๊ฒ๋๋ค.
์ ๋ JavaScript์ RxJS ๋ฅผ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋๋ฌธ์ ํ์ต ๋๊ตฌ๋ก ์ ํํ์ต๋๋ค. JavaScript๋ ์ง๊ธ ์์ ์์ ๊ฐ์ฅ ๋๋ฆฌ ์๋ ค์ง ์ธ์ด ์ค ํ๋์ด๊ณ , Rx* library family๋ ๋ง์ ์ธ์ด์ ํ๋ซํผ์์ ์ฌ์ฉ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ๋๋ค. (.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy, etc). ๊ทธ๋์, ๋น์ ์ด ์ด๋ค ๋๊ตฌ๋ฅผ ์ฐ๋ ๊ฐ์, ์ด ํํ ๋ฆฌ์ผ์ ๋ฐ๋ผํ ์ ์์๊ฒ๋๋ค.
ํธ์ํฐ์ ๋ณด๋ฉด, ๋น์ ์ด ํ๋ก์ฐ ํ ๋งํ ๋ค๋ฅธ ๊ณ์ ์ ์ถ์ฒํด์ฃผ๋ UI ์์๊ฐ ์์ต๋๋ค.
์ฐ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ ํต์ฌ ๊ธฐ๋ฅ๋ค์ ๋ชจ๋ฐฉํด๋ณด๊ณ ์ ํฉ๋๋ค:
- ํ๋ฉด์ด ๊ฐ์๋๋ฉด, API๋ก๋ถํฐ ๊ณ์ ์ ๋ณด๋ฅผ ๋ก๋ํ๊ณ , 3๊ฐ๋ฅผ ์ถ์ฒํฉ๋๋ค.
- "Refresh"๋ฅผ ๋๋ ์ ๋, ์๋ก์ด 3๊ฐ์ ๊ณ์ ์ ๋ก๋ํ๊ณ 3ํ์ผ๋ก ๋ฃ์ต๋๋ค.
- ๊ณ์ ํ์์ 'x'๋ฒํผ์ ๋๋ ์ ๋, ํด๋น ๊ณ์ ๋ง ๋น์๋ด๊ณ ์๋ก์ด ๊ฒ์ ๋ณด์ฌ์ค๋๋ค.
- ๊ฐ ํ์ ๊ณ์ ์ ์ํํ์ ๊ทธ๋ค์ ํ์ด์ง๋ก ์ฐ๊ฒฐ๋ ๋งํฌ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
๊ทธ ์ธ ์ค์ํ์ง ์์ ๊ธฐ๋ฅ๊ณผ ๋ฒํผ๋ค์ ๋ฐฐ์ ํ๋๋ก ํ๊ฒ ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ , ํธ์ํฐ๊ฐ ์ต๊ทผ unauthrorized public์๊ฒ API๋ฅผ ๋ซ์๋ฒ๋ ธ๊ธฐ ๋๋ฌธ์, ํด๋น UI๋ฅผ Github ์์ ๊ตฌํํด๋ณผ๊ฒ๋๋ค. ์์ธํ๊ฑด Github API for getting users ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์์ฑ๋ ์ฝ๋๋ฅผ ๋ณด๊ณ ์ถ์ผ์๋ฉด http://jsfiddle.net/staltz/8jFJH/48/ ์ ๊ฐ์๋ฉด ๋ฉ๋๋ค.
์ด ๋ฌธ์ ๋ฅผ Rx๋ก ์ด๋ป๊ฒ ์ ๊ทผํ ๊ฒ์ธ๊ฐ? ๊ธ์, ์์ํ๊ธฐ์ ์์์, (๊ฑฐ์) ๋ชจ๋ ๊ฒ์ ์คํธ๋ฆผ์ด ๋ ์ ์๋ค. ๋ผ๋ Rx์ ์ฃผ๋ฌธ์ ๋จผ์ ์์ด๋ณธ๋ค์, ๋งจ ์ฒ์ ๊ธฐ๋ฅ๋ถํฐ ์์ํด๋ณด๋๋ก ํฉ์๋ค. "ํ๋ฉด์ด ๊ฐ์๋๋ฉด, API๋ก๋ถํฐ 3๊ฐ์ ๊ณ์ ์ ๊ฐ์ ธ์ต๋๋ค". ์ฌ๊ธฐ์ ๋ณ๋ก ํน๋ณํ๊ฒ ์์ต๋๋ค. ์ด๊ฑด ๋จ์ํ (1)์์ฒญ์ ๋ณด๋ด๊ณ (2)์๋ต์ ๋ฐ๊ณ (3)์๋ต์ ๋ ๋๋ง ํ๋ฉด ๋๋๊ฑฐ๋๊น์. ๊ทธ๋ ๋ค๋ฉด, ์ข ๋ ๋์๊ฐ์, ์ฐ๋ฆฌ์ ์์ฒญ์ ์คํธ๋ฆผ์ผ๋ก ํํ ํด๋ณด๋๋ก ํฉ์๋ค. ์ฒ์์ ๋๋ฌด ์ค๋ฒํ๋๊ฒ์ฒ๋ผ ๋๊ปด์ง์๋ ์์๊ฑฐ์์. ํ์ง๋ง ์ฐ๋ฆฐ ๊ธฐ์ด๋ถํฐ ์์ํ ํ์๊ฐ ์์ด์. ๊ทธ๋ ์ฃ ?
ํ๋ฉด์ด ๊ฐ์๋๋ฉด, ์ฐ๋ฆฌ๋ ํ๋์ ์์ฒญ๋ง ์คํํ๋ฉด ๋ฉ๋๋ค. ์ด๊ฑธ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ผ๋ก ๋ชจ๋ธ๋งํ๋ฉด, ํ๊ฐ์ ๊ฐ์ด ๋ฐ์ํ๋ ์คํธ๋ฆผ์ผ๋ก ๋ณผ ์ ์๊ฒ ๋ค์. ์ฐจํ์๋ ๋ง์ ์์ฒญ์ด ๋ฐ์ํ ์๋ ์๊ฒ ์ง๋ง, ์ผ๋จ ์ง๊ธ์, ํ๊ฐ๋ง ์๊ฐํฉ์๋ค.
--a------|->
Where a is the string 'https://api.github.com/users'
์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ์์ฒญํ๊ณ ์ ํ๋ URL์ ์คํธ๋ฆผ์ ๋๋ค. ์์ฒญ ์ด๋ฒคํธ๋ ์ฐ๋ฆฌ์๊ฒ ๋๊ฐ์ง๋ฅผ ์๋ ค์ค๋๋ค : ์ธ์ , ๊ทธ๋ฆฌ๊ณ ๋ฌด์์. (when and what). ์์ฒญ์ด ์คํ๋์ด์ง๋ "๋"๋, ์์ฒญ์ด ๋ฐ์ํ ๋์ ๋๋ค. ์์ฒญํ๋ "๊ฒ"์ ๋ฐ์ํ ๊ฐ์ ๋๋ค. ์ฆ URL์ ๋ด๊ณ ์๋ string์ด์ฃ .
๋จ์ผ ๊ฐ์ ๋ด๊ณ ์๋ ์คํธ๋ฆผ์ ๋ง๋๋ ๊ฒ์ Rx*์์ ๋งค์ฐ ์ฝ์ต๋๋ค. ์คํธ๋ฆผ์ ์ผ์ปซ๋ ๊ณต์์ ์ธ ์ฉ์ด๋ "Observable" ์ ๋๋ค. ๊ด์ฐฐ๋ ์ ์๋ค, ์ด๊ฑฐ์ฃ ๋ญ. ๊ทผ๋ฐ ์ ๋ ์ข ๋ฉ์ฒญํ ์๋ช ์ด๋ผ๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์, ๊ทธ๋ฅ ์คํธ๋ฆผ ์ด๋ผ๊ณ ๊ณ์ ๋ถ๋ฅด๊ฒ ์ต๋๋ค.
var requestStream = Rx.Observable.just('https://api.github.com/users');
๊ทธ๋ฐ๋ฐ ์ง๊ธ ์ ๊ฒ์, ํ string์ ๊ฐ์ง ์คํธ๋ฆผ์ผ ๋ฟ์ ๋๋ค. ์๋ฌด๋ฐ ์์ ๋ ํ์ง ์๊ณ ์์ด์. ๊ทธ๋์ ์ฐ๋ฆฌ๋, ์ ๊ฐ์ด ๋ฐ์(emit)ํ ๋ ๋ฌด์จ ์ผ์ด ์๊ฒจ๋๋๋ก ํด์ผํฉ๋๋ค. ์ด๊ฒ์ ์คํธ๋ฆผ์ subscribing์ ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
requestStream.subscribe(function(requestUrl) {
// execute the request
jQuery.getJSON(requestUrl, function(responseData) {
// ...
});
}
์ฌ๊ธฐ์ ์ฃผ๋ชฉํ ์ ์ ์์ฒญ ์์ ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ jQuery Ajax callback์ ์ฌ์ฉํ๊ณ ์๋ค๋ ์ ์ ๋๋ค. (๋น์ ์ด ์ฌ์ ์ง์์ ์๊ณ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค) ๊ทธ๋ฐ๋ฐ ์ ๊น, Rx๋ ๋น๋๊ธฐ์ ์ธ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ๋ค๋ฃฌ๋ค๋ฉด์์? ์ ์์ฒญ์ ๋ํ ์๋ต์ด ๋ฏธ๋ ์ด๋ ์์ ์ ๋์ฐฉํ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์๋ ์คํธ๋ฆผ์ด ๋ ์ ์๋์? ๊ฐ๋ ์ ์ผ๋ก, ๋น์ฐํ ๊ทธ๋ ๊ฒ ๋ณด์ด๋๊ตฐ์. ๊ทธ๋ผ ํ๋ฒ ์๋ํด๋ด ์๋ค.
requestStream.subscribe(function(requestUrl) {
// execute the request
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUrl)
.done(function(response) { observer.onNext(response); })
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
});
responseStream.subscribe(function(response) {
// do something with the response
});
}
Rx.Observable.create()
์ด ํ๋ ์ผ์, observer์๊ฒ (observer์ ๋ค๋ฅธ ๋ง์ "subscriber" ์
๋๋ค.) ๋ฐ์ดํฐ ์ด๋ฒคํธ(onNext()), ์๋ฌ ์ด๋ฒคํธ(onError()), ์๋ฃ ์ด๋ฒคํธ(onCompleted())๊ฐ ์ธ์ ๋ฐ์ํ๋์ง๋ฅผ ๋ช
์์ ์ผ๋ก ์๋ ค์ค์ผ๋ก์จ ์ปค์คํ
์คํธ๋ฆผ์ ๋ง๋๋ ๊ฒ์
๋๋ค. ์ฌ๊ธฐ์ ํ๊ฑด, jQuery Ajax Promise๋ฅผ ๋จ์ํ Wrap ํ๊ฑฐ์์. ์ ๊น, ๊ทธ๊ฑด ๊ณง Promise๊ฐ Observableํ๋จ ์๋ฆฌ ์๋์?
ย ย ย ย ย
๋ง์ต๋๋ค.
Observable์ Promiss++ ์ ๋๋ค. Rx์์ 'var stream = Rx.Observable.fromPromise(promise)' ๋ฅผ ํตํด์ Promise๋ฅผ ์์ฝ๊ฒ Observable๋ก ๋ณํํ ์ ์์ต๋๋ค. ์ด๊ฑธ ํ๋ฒ ์จ๋ณด๋๋ก ํ์ฃ . ๋ฑ ํ๊ฐ์ง ์ฐจ์ด๋, Observable์ด Promises/A+ ๋ฅผ ๋ฐ๋ฅด๊ณ ์์ง ์๋ค๋๊ฒ ๋ฟ์ ๋๋ค๋ง, ๊ฐ๋ ์์ผ๋ก ์ถฉ๋ํ์ง ์์ต๋๋ค. Promise๋ ๋จ์ผ ๊ฐ๋ง ๋ฐ์์ํค๋ Observable์ผ ๋ฟ์ ๋๋ค. Rx ์คํธ๋ฆผ์ ๋ค์ํ ๋ฆฌํด ๊ฐ์ ํ์ฉํ๊ธฐ ๋๋ฌธ์, Promises๋ฅผ ๋์ด์ฐ์ต๋๋ค.
๊ฝค ํ๋ฅญํ๋ค์. ๊ทธ๋ฆฌ๊ณ Observable์ด ์ด๋ป๊ฒ ์ต์ํ Promises ๋งํผ ์ ์ฉํ์ง ์ ์ ์๊ฒ ํด์ฃผ๋๊ตฐ์. ๋ง์ฝ ๋น์ ์ด Promises์ ์ ์ ๋ฌธ๊ตฌ๋ฅผ ๋ฏฟ๋๋ค๋ฉด, Rx Observables ๋ก๋ ์ด๋ค๊ฒ ๊ฐ๋ฅํ์ง ์ง์ผ๋ณด์๊ธธ ๋ฐ๋๋๋ค.
์ฐ๋ฆฌ ์์ ๋ก ๋์์ค๊ฒ ์ต๋๋ค. ๋์น๊ฐ ์ข ๋น ๋ฅด๋ค๋ฉด ์์์ฑ์
จ๊ฒ ์ง๋ง, ์ฐ๋ฆฌ๋ ๋ค๋ฅธ subscribe()
์์์ ์ฝ ๋๊ณ ์๋ ํ subscribe()
๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ฝ๋ฐฑ ์ง์ฅ์ ๋์๊ฐ ๋๋๊ตฐ์. ๋ํ, responseStream
์ ์์ฑ์ด requestStream
์๊ฒ ์์กด๋์ด ์์ต๋๋ค. ์๋ค์ํผ, Rx๋ ์๋ก์ด ์คํธ๋ฆผ์ ๋ณ๊ฐ์ ๊ฒ์ผ๋ก ์์ฑํ๊ณ ๋ณํํ๋ ๋จ์ํ ๋ฉ์ปค๋์ฆ์ ๊ฐ๊ณ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ฌ๊ธฐ์๋ ๊ทธ๋ ๊ฒ ๋ฐ๋ผ์ผ ํฉ๋๋ค.
๋น์ ์ด ๊ผญ ์์์ผํ๋ ํจ์ ์ค ํ๋๋ map(f)
์
๋๋ค. ์ด๊ฑด ์คํธ๋ฆผ A์ ๊ฐ ๊ฐ์ ์ทจํ ๋ค์, ๊ฐ ๊ฐ๋ค์๊ฒ f()
๋ฅผ ์ ์ฉ์ํค๊ณ , ์์ฑ๋ ๊ฐ์ ์คํธ๋ฆผB์ ์ง์ด๋ฃ๋ ์ผ์ ํฉ๋๋ค. ๋ง์ฝ ๊ทธ๊ฑธ ์ด ์์ฒญ, ์๋ต ์คํธ๋ฆผ์ ์ ์ฉ์ํจ๋ค๋ฉด, ์ฐ๋ฆฌ๋ ์์ฒญ URL์ ์๋ต (์คํธ๋ฆผ์ ํํ๋ฅผ ํ๊ณ ์๋) Promises์ map ์ํฌ ์ ์์ต๋๋ค.
var responseMetastream = requestStream
.map(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
์ด๋ก์จ ์ฐ๋ฆฌ๋, "metastream"์ด๋ผ๊ณ ํ๋ ๊ดด๋ฌผ์ ๋ง๋ค์ด๋์ต๋๋ค. ์ด๊ฑด ์คํธ๋ฆผ์ ์คํธ๋ฆผ์ ๋๋ค. ๊ฒ๋จน์ง๋ง์ธ์! ๋ฉํ์คํธ๋ฆผ์ ๊ฐ๊ฐ์ ๋ฐ์๋ ๊ฐ์ด(each emitted value) ๋ค๋ฅธ ์คํธ๋ฆผ์ ์๋ ๊ฒ ๋ฟ์ ๋๋ค. ์ด๊ฑด ๋ง์น ํฌ์ธํฐ ์ฒ๋ผ ์๊ฐํ ์ ์์ต๋๋ค. ๊ฐ๊ฐ์ ๋ฐ์๋ ๊ฐ(each emitted value)๋ ๋ค๋ฅธ ์คํธ๋ฆผ์ ํฌ์ธํฐ ์ ๋๋ค. ์ฐ๋ฆฌ ์์ ์์ , ๊ฐ๊ฐ์ ์์ฒญ URL์ด ์ฐ๊ด๋ ์๋ต์ ๊ฐ๊ณ ์๋ Promise ์คํธ๋ฆผ์ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ๋ก ๋งคํ๋ ๊ฒ์ผ๋ก ๋ณผ ์ ์์ต๋๋ค. (each request URL is mapped to a pointer to the promise stream containing the corresponding response.)
์๋ต์ ๋ํ ๋ฉํ์คํธ๋ฆผ์ ๊ฝค ๋ณต์กํด๋ณด์ด๋๋ฐ๋ค๊ฐ, ๋ณ๋ก ๋์๋ ์ ๋์ด ๋ณด์ ๋๋ค. ์ฐ๋ฆฌ๋ ๊ทธ๋ฅ ๋จ์ํ ์๋ต์ ๊ฐ์ง ์คํธ๋ฆผ์ ์ํ ๋ฟ์ด์์. ์ด ์คํธ๋ฆผ์ JSON ๊ฐ์ฒด๋ฅผ ๋ด๊ณ ์๋ Promise๊ฐ ์๋, ๊ทธ๋ฅ JSON object ๋ฅผ ๋ฐ์์ํค๋ฉด ๋๋ค๊ตฌ์. ๊ทธ๋ ๋ค๋ฉด ์ด์ Flatmap ์ ๋ง๋ ์ฐจ๋ก๊ฐ ๋์๊ตฐ์. Flamtmap์ ๋ฉํ์คํธ๋ฆผ์ ํ๋ฉดํ(flatten)์ํค๋ map()์ ํ ๋ฒ์ ์ ๋๋ค. ๋ค์ ๋งํ์๋ฉด, ๊ฐ์ง(branch)์คํธ๋ฆผ์์ ๋ฐ์์ํค๋ ๋ชจ๋ ๊ฒ๋ค์, ์ค๊ธฐ(trunk)์คํธ๋ฆผ์์ ๋ฐ์์ํค๋๋ก ํ๋๊ฑฐ์ฃ . ๋ฉํ์คํธ๋ฆผ์ ๋ฒ๊ทธ๊ฐ ์๋๊ณ , flatmap์ด ๊ทธ๊ฑธ ๊ณ ์น๋ค๋ ๊ฒ์ ๋๋์ฑ ์๋๋๋ค. ์ด๊ฑด ๋จ์ง Rx์ ๋น๋๊ธฐ ์๋ต์ ๋ค๋ฃจ๋ ๋๊ตฌ์ผ ๋ฟ์ด์์.
var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
์ข๋ค์. ๊ฒ๋ค๊ฐ ์๋ต ์คํธ๋ฆผ์ด ์์ฒญ ์คํธ๋ฆผ์ ๋ค๋ฐ๋ผ์ ์ ์๋๊ณ ์๊ธฐ ๋๋ฌธ์, ๋ง์ฝ ์ถํ์ ์์ฒญ ์คํธ๋ฆผ์์ ์ด๋ฒคํธ๊ฐ ๋ ๋ฐ์ํ๋ค๋ฉด, ์๋ ๋ณด์ด๋ ๊ฒ์ฒ๋ผ ์ฐ๊ด๋๋(corresponding) ์๋ต ์ด๋ฒคํธ๊ฐ ์๋ต ์คํธ๋ฆผ์์ ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์์๊ฒ๋๋ค.
requestStream: --a-----b--c------------|->
responseStream: -----A--------B-----C---|->
(lowercase is a request, uppercase is its response)
์ด์ ๋๋์ด ์๋ต ์คํธ๋ฆผ์ ๊ฐ๊ฒ ๋์ผ๋, ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ ๋๋ง ํ ์ ์๊ฒ ๋์ต๋๋ค..
responseStream.subscribe(function(response) {
// render `response` to the DOM however you wish
});
์ง๊ธ๊น์ง์ ๋ชจ๋ ์ฝ๋๋ฅผ ํฉ์น๋ฉด, ์ด๋ ๊ฒ ๋ฉ๋๋ค:
var requestStream = Rx.Observable.just('https://api.github.com/users');
var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
responseStream.subscribe(function(response) {
// render `response` to the DOM however you wish
});
์ด๋ฐ, ๊น๋ฐํ๊ฒ ์์ต๋๋ค. ์๋ต์ ์๋ JSON์๋ ์ ์ 100๋ช ์ ๋ฆฌ์คํธ๊ฐ ๋ด๊ฒจ์์ด์. API๋ page size๊ฐ ์๋, page offset๋ง ๋ช ์ํ ์ ์๋๋ก ํ๊ณ ์์ต๋๋ค. ๋๋ถ์ ์ฐ๋ฆฌ๋ 3๊ฐ์ ๋ฐ์ดํฐ ์ค๋ธ์ ํธ๋ฅผ ์ป๊ธฐ ์ํด 97๊ฐ๋ฅผ ๋ญ๋นํ๊ฒ ๋ฉ๋๋ค. ์ด ๋ฌธ์ ๋ ์ง๊ธ ๋น์ฅ์ ๋ฌด์ํ ๊ฑฐ์ง๋ง, ์ฐจํ์ ์๋ต์ ์ด๋ป๊ฒ ์บ์ฑํ๋์ง ์ดํด๋ณด๋๋ก ํ์ฃ .
์๋ก๊ณ ์นจ ๋ฒํผ์ ๋๋ฅผ ๋๋ง๋ค, ์์ฒญ ์คํธ๋ฆผ์ ์๋ก์ด URL์ ๋ฐ์์ํค๊ณ , ์ดํ์ ์ฐ๋ฆฌ๋ ์๋ก์ด ์๋ต์ ๋ฐ๊ฒ ๋ฉ๋๋ค. ์ด์ ์ฐ๋ฆฌ๋ ๋๊ฐ์ง๋ฅผ ํด์ผ ํฉ๋๋ค. ์๋ก๊ณ ์นจ ๋ฒํผ์ ํด๋ฆญ ์ด๋ฒคํธ๋ก ์ด๋ฃจ์ด์ง ์คํธ๋ฆผ์ ๋ง๋๋ ๊ฒ๊ณผ (์ฃผ๋ฌธ: ๋ชจ๋ ๊ฒ์ ์คํธ๋ฆผ์ด ๋ ์ ์์ต๋๋ค.), ์์ฒญ ์คํธ๋ฆผ์ด ์๋ก๊ณ ์นจ ํด๋ฆญ ์คํธ๋ฆผ์ ์์กดํ๋๋ก ํด์ผํฉ๋๋ค. ๊ณ ๋ง๊ฒ๋, RxJS๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ Observables๋ก ๋ง๋ค์ด์ฃผ๋ ๋๊ตฌ๊ฐ ์์ต๋๋ค.
var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
์๋ก๊ณ ์นจ ํด๋ฆญ ์ด๋ฒคํธ ์์ฒด๋ก๋ API URL์ ๋ด์๋ด์ง ๋ชปํ๊ธฐ ๋๋ฌธ์, ์ฐ๋ฆฌ๋ ๊ฐ๊ฐ์ ํด๋ฆญ์ ์ค์ URL๋ก ๋งคํ ์์ผ์ค์ผ ํฉ๋๋ค. ์๋ก๊ณ ์นจ ํด๋ฆญ ์คํธ๋ฆผ์ ๋๋คํ offset ํ๋ผ๋ฏธํฐ๋ฅผ ๋ด๊ณ ์๋ API endpoint ๋ก ๋งคํ ๋๊ฒ ํ ํ, ๊ธฐ์กด์ ๋ง๋ ์์ฒญ ์คํธ๋ฆผ์ ์ด๊ฒ์ผ๋ก ๋ฐ๊ฟ๋ด ์๋ค.
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
์ ๊ฐ ์ข ๋ฉ์ฒญํ๊ณ , ์๋ํ ํ ์คํธ๋ฅผ ์๋ง๋ค์ด๋์, ์ ๋ ๋ฐฉ๊ธ ์ฐ๋ฆฌ๊ฐ ์ด์ ์ ๋ง๋ค์๋ ๊ธฐ๋ฅ์ ๊ฐ์์์ด๋ฒ๋ ธ์ต๋๋ค. ์ด์ ์นํ์ด์ง๋ฅผ ์ฒ์ ์์ํ์ ๋ ์์ฒญ์ ์ผ์ด๋์ง ์๊ณ , ์๋ก๊ณ ์นจ ๋ฒํผ์ ํด๋ฆญํ ๋๋ง ์์ฒญ์ด ์ผ์ด๋ ๊ฒ๋๋ค. ์์ด๊ณ . ์๋ก๊ณ ์นจ์ ๋ฒํผ์ ๋๋ ์๋์ ์นํ์ด์ง๊ฐ ๊ฐ์๋์์๋ ๋๋ค ์์ฒญ์ด ์ผ์ด๋๋๋ก ํด์ผ ํ๋๋ฐ ๋ง์ด์ฃ .
์ฐ๋ฆฌ๋ ๊ฐ๊ฐ์ ์ผ์ด์ค์ ๋ํด์ ๋ถ๋ฆฌ๋ ์คํธ๋ฆผ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ด๋ฏธ ์๊ณ ์์ต๋๋ค.
var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
๊ทธ๋ฐ๋ฐ ์ด ๋๊ฐ๋ฅผ ์ด๋ป๊ฒ ํ๋๋ก "ํฉ์น "์ ์์๊น์? merge()
๋ฅผ ์ด์ฉํ๋ฉด ๋ฉ๋๋ค. ์๋ ๊ทธ๋ฆผ์ด merge๊ฐ ๋ฌด์์ ํ๋์ง ๋ณด์ฌ์ค๋๋ค.
stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
vvvvvvvvv merge vvvvvvvvv
---a-B---C--e--D--o----->
์์ฃผ ์ฝ๊ตฐ์.
var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
var requestStream = Rx.Observable.merge(
requestOnRefreshStream, startupRequestStream
);
ํํธ, ์ค๊ฐ๋จ๊ณ ์คํธ๋ฆผ์ ์ ๊ฑฐํ๋ ค๋ฉด, ์ด๋ ๊ฒ๋ ํ ์ ์์ต๋๋ค.
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
})
.merge(Rx.Observable.just('https://api.github.com/users'));
๋ ์งง๊ฒ, ๋ ๊ฐ๋ ์ฑ์ ๋์ฌ๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
})
.startWith('https://api.github.com/users');
startWith()
ํจ์๋ ๋น์ ์ด ์๊ฐํ๋ ๊ทธ ์์
์ ํฉ๋๋ค. input ์คํธ๋ฆผ์ด ์ด๋ป๊ฒ ์๊ฒจ๋จน์๋ , startWith(x)
๊ฐ ๋ง๋ค์ด๋ธ output ์คํธ๋ฆผ์, x
๋ฅผ ์์ ๋จ๊ณ์ ๊ฐ๊ฒ ๋ฉ๋๋ค.
์ด๋ถ๋ถ์ ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ์๋์ ๊ฐ์ต๋๋ค
atream A: ----c--------c----c---->
vvvv map(return E) vvvvv
stream B: ----E--------E----E---->
vvvv startWith( S ) vvvv
stream C: S---E--------E----E---->
c is click event
E is API Endpoint
S is Startup API Endpoint
์ฌ๊ธฐ์ ์ ๊ฐ map(return E) ๋ผ๊ณ ์ด ๋ถ๋ถ์ ์ ๊ธฐ์ตํด๋์๊ธธ ๋ฐ๋๋๋ค. map(f)
ํจ์ ์์ ์๋ project function f๋ input ์ธ์๊ฐ ์๊ณ , API Endpoint๋ง ๋๋๋ ค์ฃผ๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
ํ์ง๋ง ์์ง๋ ์ถฉ๋ถํ DRY ํ์ง ์๊ตฐ์. (์ฃผ์: ์ฝ๋๋ฅผ ๋ ์ถ์ฝ์์ผฐ๋ค๋ ๋ง์
๋๋ค.) ๊ทธ ์ด์ ๋ API endpoint ๋ฌธ์์ด์ด ์ฌ์ ํ ๋ฐ๋ณต๋๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ป๊ฒํ๋ฉด ์ด๊ฑธ ๊ณ ์น ์ ์์๊น์? startWith()
ํจ์์ ์ฃผ๋ชฉํด๋ด
์๋ค. ์ง๊ธ์ startWith()
ํจ์๋, API Endpoint ์คํธ๋ฆผ ์์, Startup API Endpoint๋ฅผ ๋ถ์ฌ์ ์ ์คํธ๋ฆผ์ ๋ง๋ค๊ณ ์์ต๋๋ค. ์ด๊ฑธ ๋ฐ์์ ๋ฐ๊ฟ์, refresh ํด๋ฆญ ์คํธ๋ฆผ์ด ๊ฐ์๋ ๋, ํด๋ฆญ ์ด๋ฒคํธ๊ฐ ์์๋ ๊ฒ์ฒ๋ผ "emulate"์์ผ๋ฒ๋ฆฌ๋ ๊ฒ๋๋ค. ์ฆ, startWith()
ํจ์๋ฅผ refreshClickStream ๋ค์ ๋ถ์ด๋ฉด ๋ฉ๋๋ค.
var requestStream = refreshClickStream.startWith('startup-click')
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
์ด๊ฑธ ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ์ด๋ ๊ฒ ๋ฉ๋๋ค.
atream A: ----c--------c----c---->
vvvv startWith( s ) vvvvv
stream B: s---c--------c----c---->
vvvv map(return E) vvvvv
stream C: E---E--------E----E---->
c is click event
s is startup event (in case of this, 'startup-click' string event)
E is API Endpoint
์์ ์ธ๊ธํ๋ค์ํผ, ์ฌ๊ธฐ์ ์ฌ์ฉ๋ map(f)
ํจ์์ project function์ธ f๋, input ์ธ์๋ฅผ ๋ค ๋ฌด์ํ๊ธฐ ๋๋ฌธ์, startWith()
ํจ์์์ ์ธ์๋ก ๋ค์ด๊ฐ 'startup-click'์ด๋ผ๊ณ ์ด ๋ถ๋ถ์ ์ด๋ค ๋ฌธ์์ด์ด ์๋ ์๊ด ์์ต๋๋ค.
ํ๋ฅญํฉ๋๋ค. ์ด์ ์๊น ์ ๊ฐ "๋ฉ์ฒญํ๊ณ , ์๋ํ ํ
์คํธ๋ฅผ ์๋ง๋ค์ด๋์" ๋ผ๊ณ ๋งํ๋ ๋ถ๋ถ์ ์๋ ์ฝ๋์ ์ง๊ธ ์ฝ๋๋ฅผ ๋น๊ตํด๋ณด๋ฉด, startWith()
๊ฐ ๋ฌ๋ ค์๋๊ฒ ๋นผ๊ณค ๋์ผํ๋ค๋ ๊ฒ์ ์ ์ ์์๊ฒ๋๋ค.
(์ฃผ์: ์๋ฌธ์์ startWith()
๊ฐ ๋ฑ์ฅํ๋ ๋ถ๋ถ์ ์ค๋ช
์ด ๋ค์ ๋ถ์กฑํด์, ์๋ฌธ์ ์๋ ๋๊ธ๊ณผ ๋ ํผ๋ฐ์ค ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ ๊ฐ ์ค๋ช
์ ์ข ๋ง๋ถ์์ต๋๋ค.)
์ง๊ธ๊น์ง๋, response ์คํธ๋ฆผ์ subscribe()
๋ฅผ ํตํด์ ์๊ฒจ๋ suggestion UI ์์๋ฅผ ๋ ๋๋ง ํ๋ ๋ถ๋ถ๊น์ง๋ง ๋ค๋ฃจ๊ณ ์์์ต๋๋ค. ์ด์ ์๋ก๊ณ ์นจ ๋ฒํผ์ ์ดํด๋ณด์๋ฉด, ๋ฒํผ์ ๋๋ฅธ ๋ฐ๋ก ์งํ์๋ ๊ธฐ์กด์ ์๋ 3๊ฐ์ ์ถ์ฒ์ด ์์ด์ง์ง ์๋ ๋ฌธ์ ๊ฐ ์์์ ์ ์ ์์ต๋๋ค. ์๋ก์ด ์ถ์ฒ์ ์๋ต์ด ๋์ฐฉํ ์ดํ์๋ง ์๊ฒจ๋๊ธฐ ๋๋ฌธ์
๋๋ค. UI๋ฅผ ์ข๋ ๋ณด๊ธฐ ์ข๊ฒํ๊ธฐ ์ํด์ , ์๋ก๊ณ ์นจ ๋ฒํผ์ ํด๋ฆญํ์๋ง์ ๊ธฐ์กด์ ์ถ์ฒ์ ์์ ์ฃผ๋๊ฒ ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
refreshClickStream.subscribe(function() {
// clear the 3 suggestion DOM elements
});
๊ทธ๋ฐ๋ฐ ์ด๋ฐ ๋ฐฉ์์ ์ข์ง ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ด๋ฏธ ๊ธฐ์กด์ ๋ง๋ responseStream.subscribe()
subscriber๊ฐ ์๊ธฐ ๋๋ฌธ์, ์ ์ฝ๋๋ฅผ ๋ฃ์ผ๋ฉด ์ถ์ฒ DOM์ ์ํฅ์ ๋ผ์น๋ 2๊ฐ์ subscriber๋ฅผ ๊ฐ๊ฒ ๋๋ ๊ฒ๋๋ค. ์ด๊ฑด Separation of concerns ์๋ ๋ค๋ฅธ ๊ฒ๋๋ค. ์๊น ๋ช๋ฒ ์ธ๊ธํ๋ Reactive์ ์ฃผ๋ฌธ์ ๊ธฐ์ตํ๋์?
ย ย ย ย
๊ทธ๋ ๋ค๋ฉด, ์ถ์ฒ์ ์คํธ๋ฆผ์ผ๋ก ๋ชจ๋ธ๋ง ํด ๋ด ์๋ค. ์ด ์คํธ๋ฆผ์ด ๋ฐ์(emit)์ํค๋ ๊ฐ๋ค์, ์ถ์ฒ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์๋ JSON ๊ฐ์ฒด์ ๋๋ค. ์ฐ๋ฆฌ๋ 3๊ฐ์ ์ถ์ฒ์ ๋ํด์ ๊ฐ๊ฐ ๋ถ๋ฆฌํด์ ์คํธ๋ฆผ์ ๋ง๋ค์ด๋ณผ๊น ํฉ๋๋ค. ์๋๋ ์ถ์ฒ1์ ์คํธ๋ฆผ์ผ๋ก ๋ง๋ ์ฝ๋์ ๋๋ค.
var suggestion1Stream = responseStream
.map(function(listUsers) {
// get one random user from the list
return listUsers[Math.floor(Math.random()*listUsers.length)];
});
๋๋จธ์ง suggestion2Stream
๊ณผ suggestion3Stream
์ suggestion1Stream
์ ๋จ์ง ๋ณต์ฌ ๋ถ์ฌ๋ฃ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ์ด๊ฑด ๋น์ฐํ DRYํ์ง ์์ง๋ง, ์ด ํํ ๋ฆฌ์ผ์ ์งํํ ๋ ์์๋ฅผ ์ข ๋จ์ํ๊ฒ ํด์ค ์ ์์๊ฒ๊ฐ์ต๋๋ค. ๋ง๋ถ์ฌ์, ์ด๋ฐ ์ํฉ์์ ๋ฐ๋ณต์ ์ด๋ป๊ฒ ํผํ๋์ง ๊ณ ๋ฏผํ๋๊ฑด ์ข์ ์ฐ์ต์ด ๋ ๊ฒ ๊ฐ๋ค์.
์ ์ด๋ฒ์, responseStream์ subscribe()์์ ๋ ๋๋ง์ด ์ผ์ด๋๊ฒ ํ์ง ๋ง๊ณ , ์ถ์ฒ ์คํธ๋ฆผ์์ ๋ ๋๋ง์ด ์ผ์ด๋๊ฒ ํฉ์๋ค.
suggestion1Stream.subscribe(function(suggestion) {
// render the 1st suggestion to the DOM
});
"์๋ก๊ณ ์นจ ํ ๋, ์ถ์ฒ์ ์ด๊ธฐํ ํ๋ค" ๋ถ๋ถ์ผ๋ก ๋์๊ฐ๋ฉด, ์ฐ๋ฆฌ๋ ์๋ก๊ณ ์นจ ํด๋ฆญ์ null
์ถ์ฒ ๋ฐ์ดํฐ๋ก ๋งคํ์ํฌ ์ ์๊ณ , ์ด๊ฒ์ suggesion1Stream
์ ํฉ์ณ๋ฒ๋ฆด ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ๋ง์ด์ฃ .
var suggestion1Stream = responseStream
.map(function(listUsers) {
// get one random user from the list
return listUsers[Math.floor(Math.random()*listUsers.length)];
})
.merge(
refreshClickStream.map(function(){ return null; })
);
๊ทธ๋ฆฌ๊ณ ๋ ๋๋ง์ ํ ๋, null
์ "๋ฐ์ดํฐ ์์"์ผ๋ก ์ทจ๊ธํ๋ฉด์, UI ์์๋ฅผ ๊ฐ์ถฐ๋ฒ๋ฆฌ๋ฉด ๋ฉ๋๋ค.
suggestion1Stream.subscribe(function(suggestion) {
if (suggestion === null) {
// hide the first suggestion DOM element
}
else {
// show the first suggestion DOM element
// and render the data
}
});
์ด ๊ณผ์ ๋ค์ ๊ทธ๋ฆผ์ผ๋ก ๊ทธ๋ ค๋ณด๋ฉด ์ด๋ ๊ฒ๋ฉ๋๋ค
refreshClickStream: ----------o--------o---->
requestStream: -r--------r--------r---->
responseStream: ----R---------R------R-->
suggestion1Stream: ----s-----N---s----N-s-->
suggestion2Stream: ----q-----N---q----N-q-->
suggestion3Stream: ----t-----N---t----N-t-->
N stands for null
์ถ๊ฐ์ ์ผ๋ก, ์ฐ๋ฆฌ๋ ์นํ์ด์ง ์์๋จ๊ณ์์ "๋น์ด์๋" ์ถ์ฒ์ ๋ณด์ฌ์ค ์๋ ์์ต๋๋ค. ์ด๊ฑด startWith(null)
์ ์ถ์ฒ ์คํธ๋ฆผ์ ์ถ๊ฐํด์ฃผ๋ฉด ๋ฉ๋๋ค.
var suggestion1Stream = responseStream
.map(function(listUsers) {
// get one random user from the list
return listUsers[Math.floor(Math.random()*listUsers.length)];
})
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
๊ทธ๋ฌ๋ฉด ์ด๋ ๊ฒ ๋๊ฒ ์ฃ
refreshClickStream: ----------o---------o---->
requestStream: -r--------r---------r---->
responseStream: ----R----------R------R-->
suggestion1Stream: -N--s-----N----s----N-s-->
suggestion2Stream: -N--q-----N----q----N-q-->
suggestion3Stream: -N--t-----N----t----N-t-->
์์ง ๊ตฌํํด์ผ ํ๋ ํ๊ฐ์ง ๊ธฐ๋ฅ์ด ๋จ์์์ต๋๋ค. ๊ฐ๊ฐ์ ์ถ์ฒ์ 'x' ๋ฒํผ์ ๊ฐ๊ณ ์์ด์ผํ๊ณ , ์ด ๋ฒํผ์ด ๋๋ ธ์ ๋ ๋ค๋ฅธ ์ถ์ฒ์ ๋ก๋ํด์์ผ ํฉ๋๋ค. ์ผ๋จ ๋ณด์๋ง์ ๋๋ ์๊ฐ์, 'x' ๋ฒํผ์ด ํด๋ฆญ ๋์์ ๋ ์๋ก์ด ์์ฒญ์ ๋ง๋ค๋ฉด ๋ ๊ฒ ๊ฐ์ ๋ณด์ ๋๋ค.
var close1Button = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');
// and the same for close2Button and close3Button
var requestStream = refreshClickStream.startWith('startup click')
.merge(close1ClickStream) // we added this
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
์ด๊ฑด ์๋ํ์ง ์์ต๋๋ค. ์ด๋ ๊ฒํ๋ฉด, ๋ชจ๋ ์ถ์ฒ์ ๋น์๋ฒ๋ฆฌ๊ณ ๋ชจ๋ ์ถ์ฒ์ ๋ค์ ๋ก๋ํ๊ฒ ๋ฉ๋๋ค. (์ฃผ์: refresh ๋ฒํผ ํด๋ฆญ ์คํธ๋ฆผ๊ณผ close1 ๋ฒํผ ํด๋ฆญ ์คํธ๋ฆผ์ด ๋จ์ํ merge๋์์ผ๋ฏ๋ก, ์ด ๋๊ฐ์ ๋ฒํผ ํด๋ฆญ์ ๋์ผํ ๋์์ ํ๊ฒ ๋ฉ๋๋ค) ์ด๊ฑธ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์๋๋ฐ์, ์ฐ๋ฆฌ๋ ์ด์ ์ ์๋ต์ ์ฌ์ฌ์ฉํ๋ฉด์ ํด๊ฒฐํด๋ณด๋ ค๊ณ ํฉ๋๋ค. API๊ฐ ์๋ตํ ํ์ด์ง ํฌ๊ธฐ๋ 100๋ช ์ ์ ์ ์ ๋๋ค. ์ฐ๋ฆฐ ๊ทธ์ค์์ 3๊ฐ๋ง ํ์ํ ๋ฟ์ด์์. ๋ฐ๋ผ์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ ๋ฐ์ดํฐ๊ฐ ๋ง์ด ์์ต๋๋ค. ์์ฒญ์ ๋ค์ ๋ณด๋ผ ํ์๊ฐ ์์ด์.
์ด๋ฒ์๋, ์คํธ๋ฆผ์ผ๋ก ์๊ฐํด๋ด ์๋ค. 'close1' ํด๋ฆญ ์ด๋ฒคํธ๊ฐ ์๊ฒจ๋ฌ์ ๋, ์ฐ๋ฆฌ๋ ์๋ต ์คํธ๋ฆผ ์ค์์ ๊ฐ์ฅ ์ต๊ทผ์ ๋ฐ์ํ ์๋ต์ ์ฌ์ฉํ๊ณ ์ถ์ต๋๋ค. ์ด ์๋ต์ด ๊ฐ๊ณ ์๋ ๋ฆฌ์คํธ์์ ํ๋ช ์ ๋๋คํ ์ ์ ๋ฅผ ๊ฐ์ ธ์ค๋ฉด ๋๋๊ฒ๋๋ค. ์ด๋ ๊ฒ์
requestStream: --r--------------->
responseStream: ------R----------->
close1ClickStream: ------------c----->
suggestion1Stream: ------s-----s----->
Rx*์์ ์ด๋ฐ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ํจ์๋ก๋, combineLatest
๋ผ๋ ์กฐํฉ ํจ์(combinator function)๊ฐ ์์ต๋๋ค. ์ด ํจ์๋, ๋๊ฐ์ ์คํธ๋ฆผ์ ์
๋ ฅ์ผ๋ก ๋ฐ์ ๋ค, ๊ฐ๊ฐ์ ์
๋ ฅ ์คํธ๋ฆผ์ด ์๋ก์ด ๊ฐ์ ๋ฐ์(emit)์ํฌ ๋, combineLastest
ํจ์๋ ๊ฐ๊ฐ ์คํธ๋ฆผ์ ๊ฐ์ฅ ์ต๊ทผ ๊ฐ์ ์ด์ฉํด์ ๋ง๋ ์๋ก์ด ๊ฐ์ ๋ฐ์์ํต๋๋ค. ๊ฐ๊ฐ ์
๋ ฅ ์คํธ๋ฆผ์ ๊ฐ์ฅ ์ต๊ทผ ๊ฐ์ a
, b
๋ผ๊ณ ํ๊ณ , combineLatest
์์ ์ ์ํ project function์ f
๋ผ๊ณ ํ๋ฉด, ์๋ก ๋ง๋ค์ด์ง๋ ๊ฐ์ c=f(a,b)
์
๋๋ค. ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ์๋์ ๊ฐ์์
(์ฃผ์: ์๋ฌธ์ ๋์์๋ diagram์ด ์คํดํ ์ ์๋ ๋ถ๋ถ์ด ์์ด์ ๊ณต์ ๋ ํผ๋ฐ์ค์์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์์ต๋๋ค)
์ฐ๋ฆฌ๋ combineLatest()
ํจ์๋ฅผ close1ClickStream
๊ณผ responseStream
์ ์ ์ฉ์์ผ๋ณด๊ฒ ์ต๋๋ค. close1 ๋ฒํผ์ด ํด๋ฆญ๋ ๋๋ง๋ค, ๊ฐ์ฅ ๋ง์ง๋ง์ผ๋ก ๋ฐ์ํ ์๋ต์ ์ฌ์ฉํด์ suggestion1Stream
์ ๋ค์ด๊ฐ ์๋ก์ด ๊ฐ์ผ๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฒ๋๋ค. ํํธ, combineLatest()
๋ ๋์นญ์ ์
๋๋ค. ๋ค์ ๋งํ์๋ฉด, responseStream
์์ ์๋ก์ด ๊ฐ์ด ๋ฐ์ํ๊ฒ ๋๋ฉด, close1
ํด๋ฆญ๊ณผ ์กฐํฉ๋์ด์ ์๋ก์ด ์ถ์ฒ์ ์์ฑํ๋ค๋ ์๋ฆฌ์ฃ . ํฅ๋ฏธ๋กญ๊ตฐ์. ์ด๊ฑธ ์ ์ด์ฉํ๋ฉด ์ด์ ์ ๋ง๋ suggestion1Stream
์ ๋จ์ํ ํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค. ์ด๋ ๊ฒ ๋ง์ด์ฃ .
var suggestion1Stream = close1ClickStream
.combineLatest(responseStream,
function(click, listUsers) {
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
์์ง ํ๊ฐ์ง ๋จ์ ๊ฒ์ด ์์ต๋๋ค. combineLatest()
๋ ๋ input ์์ ๊ฐ์ฅ ์ต๊ทผ์ ๊ฒ์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ input ์ค์์ ์์ง ์๋ฌด๊ฒ๋ ๋ฐ์์ํค์ง ์์ ๊ฒ์ด ์๋ค๋ฉด, combineLatest()
๋ output ์คํธ๋ฆผ์ผ๋ก ์๋ฌด๋ฐ ๋ฐ์ดํฐ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค์ง ๋ชปํ๋ค๋๊ฑฐ์ฃ . ์์ combineLatest()
์ ๋ค์ด์ด๊ทธ๋จ์ ๋ณด๋ฉด, ์ฒซ๋ฒ์งธ input ์คํธ๋ฆผ์ด a
๋ฅผ ๋ฐ์ํ์๋, ๋๋ฒ์งธ input์คํธ๋ฆผ์ ์๋ฌด ๊ฒ๋ ๋ฐ์ํ์ง ์์ ์ํ๋ผ์, output์ผ๋ก ๋์ค๋ ๊ฐ์ ์์ง ์์ต๋๋ค. ๋๋ฒ์งธ input์คํธ๋ฆผ์ด 1
์ ๋ฐ์ํ์ ๋ ๋น๋ก์ output๊ฐ์ธ a1
์ด ๋์ค๊ฒ ๋์ฃ .
์ด๊ฑธ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์ญ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ต๋๋ค๋ง, ๊ฐ์ฅ ๋จ์ํ ๋ฐฉ๋ฒ์ ์จ๋ณผ๊น ํฉ๋๋ค. ์นํ์ด์ง ์์ ๋จ๊ณ์์, 'close1' ๋ฒํผ์ด ํด๋ฆญ๋ ๊ฒ์ฒ๋ผ ๋ง๋ค์ด์ฃผ๋๊ฑฐ์ฃ .
var suggestion1Stream = close1ClickStream.startWith('startup click') // we added this
.combineLatest(responseStream,
function(click, listUsers) {l
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
๋ค ํ์ต๋๋ค. ์์ฑ๋ ์ฝ๋๋ ์ด๊ฒ๋๋ค.
var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
var closeButton1 = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
// and the same logic for close2 and close3
var requestStream = refreshClickStream.startWith('startup click')
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var responseStream = requestStream
.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
});
var suggestion1Stream = close1ClickStream.startWith('startup click')
.combineLatest(responseStream,
function(click, listUsers) {
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
.merge(
refreshClickStream.map(function(){ return null; })
)
.startWith(null);
// and the same logic for suggestion2Stream and suggestion3Stream
suggestion1Stream.subscribe(function(suggestion) {
if (suggestion === null) {
// hide the first suggestion DOM element
}
else {
// show the first suggestion DOM element
// and render the data
}
});
** http://jsfiddle.net/staltz/8jFJH/48/** ์ ์ค์๋ฉด ์ค์ ๋์ํ๋ ์์ ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
์ ์ฝ๋๋ ์งง์ง๋ง ๊ฝค ๋ง์ ๊ฒ์ ํจ์ถํ๊ณ ์์ต๋๋ค. ์ ์ ํ๊ฒ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ ๋ค์ํ ์ด๋ฒคํธ๋ค์ ์ฒ๋ฆฌํ๊ณ ์๊ณ , ์ฌ์ง์ด ์๋ต์ ์บ์ฑ๋ ํ๊ณ ์์ต๋๋ค. ์ด๋ฐ ํจ์ํ ์คํ์ผ์ ์ฝ๋๋ฅผ ๋ช
๋ นํ(imperative)๊ฐ ์๋๋ผ, ์ ์ธ์ (declarative)๋ก ๋ง๋ญ๋๋ค. ์ฐ๋ฆฌ๋ ์คํํด์ผ ํ๋ sequence of instruction์ ์ฃผ๋ ๊ฒ์ด ์๋๋ผ, ์ด๊ฒ ๋ญ์ง ๋งํ๊ณ ์์ ๋ฟ์
๋๋ค. ์๋ฅผ ๋ค์ด, ์ฐ๋ฆฌ๋ Rx๋ก, ์ปดํจํฐ์๊ฒ, suggestion1Stream
์ close1
์คํธ๋ฆผ๊ณผ, ๊ฐ์ฅ ์ต๊ทผ์ ์๋ต์ผ๋ก๋ถํฐ ๊ฐ์ ธ์จ ์ ์ ํ๋ช
์ ์กฐํฉํ๋ ๊ฒ์ด๊ณ , ์๋ก๊ณ ์นจ์ด๋ ์นํ์ด์ง๊ฐ ์์ํ์ ๋ null
์ด๋ผ๊ณ ๋งํ๊ณ ๋งํ๊ณ ์๋๊ฒ๋๋ค.
๋ํ ์ฃผ๋ชฉํด์ผ ํ ์ ์, if
, for
, while
๊ฐ์ control flow ์์์, JavaScript ์ดํ๋ฆฌ์ผ์ด์
์์ ํญ์ ๋ค์ด๊ฐ๋ ์ฝ๋ฐฑ ๊ตฌ๋ฌธ์ด ์ ํ ๋ณด์ด์ง ์๋ ๋ค๋ ์ ์
๋๋ค. ์ ์ฝ๋์์ subscribe()
์ ์๋ if
, else
๋ filter()
๋ฅผ ์ฌ์ฉํด์ ์์จ ์ ์์ต๋๋ค. (๋น์ ์ด ์ฐ์ต์ผ์ ํด๋ณผ ์ ์๋๋ก ์ ๊ฐ ๊ตฌํํด๋์ง ์์์ต๋๋ค) Rx์์, ์ฐ๋ฆฌ๋ map
, filter
, scan
, merge
, combineLatest
, startWith
์ธ์๋ ๋ ๋ง์ ์คํธ๋ฆผ ํจ์๋ค์ ์ฌ์ฉํจ์ผ๋ก์จ, ์ด๋ฒคํธ ๊ธฐ๋ฐ ํ๋ก๊ทธ๋จ์ ํ๋ฆ์ ์ ์ด ํ ์ ์์ต๋๋ค. ์ด๋ฐ ๋๊ตฌ๋ค์ ์ ์ ์ฝ๋๋ก๋ ๋ ๋ง์ ์ผ์ ํ ์ ์๊ฒ ํด์ค๊ฒ๋๋ค.
๋ง์ฝ Reactive Programming์ ํ๊ธฐ ์ํด์ Rx*๊ฐ ๊ด์ฐฎ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๊ณ ๋๊ปด์ง๋ค๋ฉด, ์๊ฐ์ ์ข ๋ด์ด์ big list of functions ์ ์๋ ๋ณํ, ์กฐํฉ, ๊ทธ๋ฆฌ๊ณ ์์ฑ๊ณผ ๊ด๋ จ๋ ๋ถ๋ถ์ ์ฝ์ด๋ณด๊ธธ ๋ฐ๋๋๋ค. ๋ง์ฝ ํด๋น ํจ์๋ค์ ์คํธ๋ฆผ ๋ค์ด์ด๊ทธ๋จ์ผ๋ก ์ดํดํ๊ณ ์ถ๋ค๋ฉด, RxJava's very useful documentation with marble diagrams ์ ๋ณด๊ธธ ๋ฐ๋๋๋ค. ๋ญ๊ฐ๋ฅผ ํ๋ค ๋งํ๋ค๋ฉด, ๋ค์ด์ด๊ทธ๋จ์ ๋จผ์ ๊ทธ๋ฆฐ ๋ค์, ๋ค์ด์ด๊ทธ๋จ์ผ๋ก ์๊ฐํ๊ณ , ์ญ ๋์ด๋ ํจ์๋ค์ ๋ชฉ๋ก์ ์ณ๋ค๋ณด๋ฉด์, ๋ ์๊ฐํด๋ณด์๊ธธ ๋ฐ๋๋๋ค. ์ ๊ฒฝํ์ ์ด๋ฐ ๋ฐฉ์์ ํจ๊ณผ์ ์ด์์ต๋๋ค.
๋น์ ์ด Rx*๋ก ํ๋ก๊ทธ๋๋ฐ์ ํ ์ค ์๊ฒ ๋๋ฉด, Cold vs Hot Observables ์ ๊ฐ๋ ์ ์ดํดํ๋๊ฑด ๋ฐ๋์ ํ์ํฉ๋๋ค. ์ด๊ฑธ ๋ฌด์ํ๋ฉด, ์ธ์ ๊ฐ ์น๋ช ์ ์ธ ๋ฌธ์ ๋ก ๋๋์์ฌ๊ฒ๋๋ค. ๋ถ๋ช ๊ฒฝ๊ณ ํ์ด์. ์ง์ง ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ๋ฐฐ์ฐ๋ฉด์ ์ค๋ ฅ์ ๋ ํค์ฐ์๊ณ , and getting acquainted with issues such as side effects that affect Rx*.
ํ์ง๋ง Reactive Programming์ Rx* ๋ฟ๋ง์ด ์๋๋๋ค. Rx* ๋ฅผ ํ๋ค๋ณด๋ฉด ๊ฐ๋ ์ด์ํ ์ ๋ค์ ๋๋ ๋๊ฐ ์์ ํ ๋ฐ, Bacon.js ์ ์ฐ๋ฉด ์ง๊ด์ ์ผ๋ก ์์ ํ ์ ์์ต๋๋ค. Elm Language ์, JavaScript + HTML + CSS ์ผ๋ก ์ปดํ์ผ๋๋ Functional Reactive Programming ์ธ์ด๋ก์จ, ๋ ์์ ์ธ ๋ถ์ผ๋ฅผ ๊ตฌ์ถํ๊ณ ์์ต๋๋ค. Elm์๋ time travelling debugger ์ด๋๊ฒ ์๋๋ฐ, ๊ต์ฅํ ๊ธฐ๋ฅ์ ๋๋ค.
Rx๋ ์ด๋ฒคํธ๊ฐ ๋์ณ๋๋ ํ๋ก ํธ์๋์ ์ฑ ๋จ์์ ํ๋ฅญํ๊ฒ ์ฐ์ผ ์ ์์ต๋๋ค. ํ์ง๋ง ๋จ์ง ํด๋ผ์ด์ธํธ ์ชฝ ๋ฟ๋ง ์๋๋ผ, ๋ฐฑ์๋, DB์ ์ฐ๊ฒฐ๋๋ ๋ถ๋ถ์์๋ ํ๋ฅญํ๊ฒ ์ธ ์ ์์ต๋๋ค. ์ฌ์ค RxJava๋ Netflix์ API์์ ์๋ฒ์ฌ์ด๋ concurrency๋ฅผ ๊ฐ๋ฅ์ผํ๋ ํต์ฌ ๋ถ๋ถ์ ๋๋ค. Rx๋ ํน์ ํ์ ์ ์ดํ๋ฆฌ์ผ์ด์ ์ด๋ ์ธ์ด์ ์ฐ์ด๋ ํ๋ ์์ํฌ ์์ค์ ์ ํ๋์ด ์์ง ์์ต๋๋ค. ์ด๊ฒ์ ์ด๋ฒคํธ-๊ธฐ๋ฐ ์ํํธ์จ์ด๋ฅผ ํ๋ก๊ทธ๋๋ฐํ ๋ ์ธ ์ ์๋ ํจ๋ฌ๋ค์ ๊ทธ ์์ฒด์ ๋๋ค.
์ด ํํ ๋ฆฌ์ผ์ด ๋์์ด ๋์๋ค๋ฉด, ํธ์ํฐ๋ก ๊ณต์ ํด์ฃผ์ธ์.
keyword: rxjs, rxjs ์ ๋ฌธ, rxjs ๊ฐ์ด๋, rxjs ๊ธฐ์ด, rxjs ์๊ฐ, rxjs ์ค๋ช , rxjs ์ธํธ๋ก, rxjs intro, rxjs ์ด๋