์ด๊ธ์ @andrestaltz์ "The introduction to Reactive Programming you've been missing" ( https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 )๋ฅผ ๋ฒ์ญํ ๊ธ์ ๋๋ค. (๋ฒ์ญ ํ๋ฝ์ ๋ฐ์ ๊ฒ์ ์๋์ง๋ง, ๋์์ด ํ์ํ์ ๋ถ๋ค์ ์ํด์ ์ฌ๋ ค๋ด ๋๋ค. ๋ฌธ์ ๊ฐ ๋ ๊ฒฝ์ฐ ๋ฐ๋ก ์ญ์ ํ๊ฒ ์ต๋๋ค. ใ กใ )
๋ง์ฝ ์ฌ๋ฌ๋ถ์ด ๋ผ์ด๋ธ ์ฝ๋ฉ์ ํฌํจํ ๋์์ ํํฐ๋ฆฌ์ผ์ ๋ณด๋ ๊ฒ์ ๋ ์ ํธํ๋ค๋ฉด, ์ด ๋์์ ์๋ฆฌ์ฆ๋ฅผ ํ์ธํด๋ณด์ธ์. ์ด ๊ธ๊ณผ ๋์ผํ ๋ด์ฉ์ผ๋ก ๋ นํํ์ต๋๋ค. ( https://egghead.io/series/introduction-to-reactive-programming )
์ฌ๋ฌ๋ถ์ Reactive Programming ์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ ์ด ์๋ก์ด ๊ฒ์ ๋ฐฐ์ฐ๋๋ฐ ๊ด์ฌ์ด ์์ต๋๋ค. ํนํ Reactive Programming ๋ณํ๋ค์ Rx, Bacon.js, RAC ๊ทธ๋ฆฌ๊ณ ๋ค๋ฅธ ๊ฒ๋ค์ ๊ตฌ์ฑํ๊ณ ์์ฃ .
๋ฐฐ์ฐ๋ ๊ฒ์ ์ด๋ ต์ต๋๋ค. ์ฌ์ง์ด ์ข์ ์๋ฃ๊ฐ ๋ถ์กฑํ ์๋ก ๋ ๋ฐฐ์ฐ๊ธฐ ์ด๋ ต์ง์. ์ ๊ฐ ๋ฐฐ์ฐ๊ธฐ ์์ํ์ ๋, ํํฐ๋ฆฌ์ผ์ ๋ณด๋ ค๊ณ ๋ ธ๋ ฅํ์ต๋๋ค. ์ ๋ ๊ฒจ์ฐ ๋ช๊ฐ์ ์ค์ฉ์ ์ธ ๊ฐ์ด๋๋ง ์ฐพ์ ์ ์์์ง์. ํ์ง๋ง ๊ทธ ํํฐ๋ฆฌ์ผ๋ค์ ๊ฒํ๋ฉด๋ง ๊ธ๋ ์์ค์ด์๊ณ , ์ค์ ์ ์ฒด architecture๋ฅผ ๊ตฌ์ฑํ ๋ ๊ฒช๋ ๋ฌธ์ ๋ค์ ํด๊ฒฐํด์ฃผ๋ ๊ฒ๋ค์ ์์์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฌธ์๋ค์ ์ด๋ค ํจ์๋ฅผ ์ดํดํ๋ ค๊ณ ํ ๋ ์ข ์ข ๋์์ด ์๋์์ต๋๋ค. ๊ทธ๋ฌ๋๊น, ์์งํ, ์ด๋ฐ ๊ฑธ ๋ณด์ธ์.
Rx.Observable.prototype.flatMapLatest(selector, [thisArg])
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 ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ๋ฒ์ผ๋ก ๊ณง์ฅ ๋ฐ์ด๋๋ ๊ทธ๋ฐ ๊ฒ์ด์์ต๋๋ค.์ ๋ ๊ฒฐ๊ตญ ๊ณ ์ํด์ Reactive Programming ํ์ต์ ๋ง์ณค์ต๋๋ค. ๋ง๋ค๋ฉด์ ๋ฐฐ์ ์ต๋๋ค. Futurice์ ์ง์ฅ์์ ์ค์ ํ๋ก์ ํธ์ Reactive Programming์ ์ฌ์ฉํ๊ณ , ๊ณค๊ฒฝ์ ์ฒํ ๋ ๋๋ฃ์ ๋์( http://blog.futurice.com/top-7-tips-for-rxjava-on-android )๋ ์์์ต๋๋ค.
๋ฐฐ์์ ๊ธธ์ ๊ฐ์ฅ ์ด๋ ค์ด ๋ถ๋ถ์ Reactive ๋ฐฉ์์ผ๋ก ์๊ฐํ๋ ๊ฒ์ด์์ต๋๋ค. ์ ํ์ ์ธ ํ๋ก๊ทธ๋๋ฐ์ ๋ช ๋ นํ์ด๊ณ ์ํ๋ฅผ ์ ์งํ๋ ์๋ ๋ฐฉ์์ ๋ ๋ ๋ณด๋ด๋ ๊ฒ๊ณผ ์ ์ ๋๊ฐ ๋ค๋ฅธ ํจ๋ฌ๋ค์์ผ๋ก ์ผํ๋๋ก ํ๊ฒ ํ๋ ๊ฒ์ด ์ปธ์ต๋๋ค. ์ ๋ ์ด๋ฐ ๊ด์ ์์์ ์ด๋ ํ ๊ฐ์ด๋๋ ์ฐพ์ ์ ์์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ธ์์ Reactive ๋ฐฉ์์ผ๋ก ์๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์ค์ฉ์ ์ธ ํํฐ๋ฆฌ์ผ์ด ํ๋์ฏค์ ์์ด์ผ ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค. ๊ทธ๋์ ์์ํ ์ ์์์ต๋๋ค. Reactive ๋ฐฉ์์ผ๋ก ์๊ฐํ๋ ๊ฒ ์ดํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฌธ์๊ฐ ์ฌ๋ฌ๋ถ์ ๊ธธ์ ๋ฐํ์ค ์ ์์ต๋๋ค. ์ด ๊ธ์ด ์ฌ๋ฌ๋ถ์๊ฒ ๋์์ด ๋๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค.
์ธํฐ๋ท์ ๋์ ์ค๋ช ๊ณผ ์ ์๊ฐ ๋งค์ฐ ๋ง์ต๋๋ค. Wikipedia( https://en.wikipedia.org/wiki/Reactive_programming )๋ ๋ณดํต ๋๋ฌด ์ผ๋ฐ์ ์ด๊ณ ์ด๋ก ์ ์ ๋๋ค. Stackoverflow( http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming )์ ๊ท๋ฒ์ ์ธ ๋ต๋ณ์ ๋ช ๋ฐฑํ ์ด์ฌ์๋ค์๊ฒ๋ ์ ์ ํ์ง ์์ต๋๋ค. Reactive Manifesto( http://www.reactivemanifesto.org/ )๋ ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ ๋งค๋์ ํน์ ์ฌ๋ฌ๋ถ์ ํ์ฌ์ ๊ฒฝ์์ ์๊ฒ ๋ณด์ฌ์ฃผ๊ธฐ ์ํ ๊ฒ๋ค ์ฒ๋ผ ๋ค๋ฆฝ๋๋ค. ๋ง์ดํฌ๋ก์ํํธ์ Rx ์ฉ์ด( https://rx.codeplex.com/ ) "Rx = Observables + LINQ + Schedulers"๋ ๋๋ฌด ๋ฌด๊ฒ๊ณ ๋๋ฌด ๋ง์ดํฌ๋ก์ํํธ์ค๋ฌ์์ ์ฐ๋ฆฌ ๋๋ถ๋ถ์ ํผ๋ ์ค๋ฝ๊ฒ ๋ง๋ญ๋๋ค. "reative" ์ "๋ณ๊ฒฝ์ฌํญ์ ์ ํ"("propagation of change")๊ฐ์ ์ฉ์ด๋ ์ ํ์ ์ธ MV* ์ ์ธ๊ธฐ์๋ ์ธ์ด๋ค์ด ์ด๋ฏธ ์ ํํ๋ ๊ฒ๋ค๊ณผ๋ ํน๋ณํ ๋ค๋ฅธ ๊ฒ์ ์ ๋ฌํ์ง ๋ชปํฉ๋๋ค. ๋ฌผ๋ก ์ ์ framework view๋ค์ model๋ค์ ๋ฐ์(react)ํฉ๋๋ค. ๋ฌผ๋ก ๋ณ๊ฒฝ์ฌํญ์ ์ ํ๋ฉ๋๋ค. ๊ทธ๋ ์ง ์๋ค๋ฉด, ์๋ฌด๊ฒ๋ ๊ทธ๋ ค์ง์ง ์์ ๊ฒ๋๋ค.
๊ทธ๋์ ํ์๋ฆฌ ์ง์ด์น์์๋ค.
Reactive programming์ ๋น๋๊ธฐ data ํ๋ฆ๋ค(asynchronous data streams)๋ก ํ๋ก๊ทธ๋๋ฐํ๋ ๊ฒ๋๋ค.
ํํธ์ผ๋ก๋ Reactive programming์ ์๋ก์ด ๊ฒ์ด ์๋๋๋ค. ์ด๋ฒคํธ ๋ฒ์ค๋ค ํน์ ์ฌ๋ฌ๋ถ์ ์ ํ์ ์ธ ํด๋ฆญ ์ด๋ฒคํธ๋ค์ ์ฌ์ค์ ๋น๋๊ธฐ ์ด๋ฒคํธ ํ๋ฆ(stream)์ ๋๋ค. ์ฌ๋ฌ๋ถ์ด ๊ด์ฐฐํ ์ ์๊ณ , ๋ช๊ฐ์ง Side effect(ํจ์์ ์ ๋ ฅ๊ณผ ์ถ๋ ฅ์ฌ์ด ์ฐ๊ด ๊ด๊ณ ์์ด ๋์ํ๋ ๊ฒ)๋ค์ ํ ๋น๋๊ธฐ ์ด๋ฒคํธ stream์ ๋๋ค.
์ฌ๋ฌ๋ถ์ ๋จ์ํ clickํ๊ณ hoverํ๋ ์ด๋ฒคํธ๋ฟ๋ง ์๋๋ผ ์ด๋ค ๊ฒ์ด๋ผ๋ data stream์ ๋ง๋ค ์ ์์ต๋๋ค. stream๋ค์ ์ฐ์ฐ ๋น์ฉ์ด ์ ๋ ดํ๊ณ ์ด๋์๋ ์กด์ฌํฉ๋๋ค. ์ด๋ค ๊ฒ์ด๋ stream์ด ๋ ์ ์์ต๋๋ค.:๋ณ์, ์ฌ์ฉ์ ์ ๋ ฅ, property๋ค, cache๋ค, ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ค ๊ธฐํ๋ฑ๋ฑ. ์๋ฅผ ๋ค์ด, click ์ด๋ฒคํธ๋ค๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ฌ๋ฌ๋ถ์ Twitter ํผ๋๋ data stream์ผ ๊ฒ์ด๋ผ๊ณ ์์ํด๋ณด์ธ์. ์ฌ๋ฌ๋ถ์ ๊ทธ stream์ ๋ฃ๊ณ , ๊ทธ์ ๋ฐ๋ผ ๋ฐ์ํ ์ ์์ต๋๋ค.
์ด์ ๋๋ถ์ด, ๊ทธ๋ฐ stream๋ค์ ๊ฒฐํฉํ๊ณ (combine), ์์ฑํ๊ณ , ๊ฑฐ๋ฅด๋(filter) ํจ์๋ค์ ๋๋ผ์ด ๋๊ตฌ์์๊ฐ ์ฌ๋ฌ๋ถ์๊ฒ ์ฃผ์ด์ก์ต๋๋ค.
์ฌ๊ธฐ์ "functional"์ ๋ง์ ์ด ํจ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. stream์ ๋ค๋ฅธ stream์ ์ ๋ ฅ์ผ๋ก ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ์ฌ์ง์ด ์ฌ๋ฌ๊ฐ์ stream๋ค์ด ๋ค๋ฅธ stream์ ์ ๋ ฅ์ผ๋ก ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ๋๊ฐ์ stream๋ค์ ํฉ์น ์(merge)๋ ์์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ๊ด์ฌ์๋ ์ด๋ฒคํธ๋ง์ ๊ฐ์ง๋ stream์ ์ป๊ธฐ ์ํด์ stream์ ๊ฑฐ๋ฅผ์(filter) ์์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ํ stream์ผ๋ก ๋ถํฐ์ data ๊ฐ์ ๋ค๋ฅธ ์๋ก์ด stream์ผ๋ก ์ฌ์ํ (map - ์ด๋ค ๊ฐ(์ด๋ฒคํธ)๋ฅผ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ณํ)์๋ ์์ต๋๋ค.
stream์ ์๊ฐ ์์๋๋ก ์งํ์ค์ธ ์ด๋ฒคํธ๋ค์ ์์๋ฐฐ์ด(sequence)์ ๋๋ค. stream์ ์ธ๊ฐ์ง ๋ค๋ฅธ ๊ฒ๋ค์ ๋ฐ์์ํฌ(emit) ์ ์์ต๋๋ค.: (์ด๋ค ํ์ ์) ๊ฐ, ์ค๋ฅ(error), "์๋ฃ๋จ" ์ ํธ("completed" signal). "completed" ์ ํธ๊ฐ ๋ฐ์ํ๋ค๊ณ ๊ฐ์ ํด๋ณด์ธ์. ์๋ฅผ ๋ค์ด, ๋ฒํผ์ ํฌํจํ ํ์ฌ ์๋์ฐ ํน์ view๊ฐ ๋ซํ์๋(closed)๋ฅผ ๊ฐ์ ํด ๋ณด์ธ์.
์ฐ๋ฆฌ๋ ์ด ๋ฐ์ํ(emitted) ์ด๋ฒคํธ๋ค์ ๋จ์ง ๋น๋๊ธฐ๋ก(asynchronously) ํ๋ํ ์ ์์ต๋๋ค. ๊ฐ์ด ๋ฐ์ํ (emitted) ๋ ์คํ๋๋ ํจ์, ์ค๋ฅ(error)๊ฐ ๋ฐ์ํ (emitted) ๋ ์คํ๋๋ ๋ค๋ฅธ ํจ์, "completed" ์ ํธ๊ฐ ๋ฐ์ํ (emitted) ๋ ์คํ๋๋ ๋ค๋ฅธ ํจ์๋ค์ ์ ์ ํจ์ผ๋ก์จ ์ด๋ฒคํธ๋ฅผ ํ๋ํ ์ ์์ต๋๋ค. ๋ง์ง๋ง ๋ ๊ฐ์ง(error, "completed" signal)์ ๊ฐ๋ ๋ฐ์ํ๊ธฐ(emitted) ๋๋ฌธ์, ์ฌ๋ฌ๋ถ์ ๊ฐ๋ค์ ๋ํ ํจ์๋ฅผ ์ ์ํ๋๋ฐ๋ง ์ง์คํ ์ ์์ต๋๋ค. stream์ ๋ฃ๋("listening") ๊ฒ์ subscribing ์ด๋ผ๊ณ ํฉ๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ ์ํ ํจ์๋ค์ observer๋ค์ ๋๋ค. stream์ ๊ด์ฐฐ๋๋ subject(ํน์ "observable")์ ๋๋ค. ์ด๊ฒ์ด ํ์คํ Observer Design Pattern( https://en.wikipedia.org/wiki/Observer_pattern )์ ๋๋ค.
๊ทธ๋ฐ ๋ค์ด์ด๊ทธ๋จ์ ๊ทธ๋ฆฌ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ASCII๋ฅผ ์ด์ฉํ๋ ๊ฒ๋๋ค. ์ด ํํฐ๋ฆฌ์ผ์ ๋ช ๋ถ๋ถ์์ ASCII๋ฅผ ์ฌ์ฉํ ๊ฒ๋๋ค.
--a---b-c---d---X---|->
a, b, c, d ๋ ๋ฐ์ํ(emitted) ๋ ๊ฐ๋ค์
๋๋ค.
X ๋ ์ค๋ฅ(error)์
๋๋ค.
| ๋ "completed" ์ ํธ์
๋๋ค.
---> ๋ "timeline" ์
๋๋ค.
์ด๋ฐ ๊ฒ๋ค์ ์ด๋ฏธ ์ถฉ๋ถํ ์น์ํ๊ณ , ์ง๋ฃจํด์ง๊ณ ์ถ์ง ์๊ธฐ ๋๋ฌธ์, ์๋ก์ด ๊ฒ์ ํด๋ด ์๋ค. ์๋์ click ์ด๋ฒคํธ stream ๋ก๋ถํฐ ๋ณํ๋ ์๋ก์ด click ์ด๋ฒคํธ stream๋ค์ ์์ฑํ ๊ฒ๋๋ค.
์ฒซ๋ฒ์งธ๋ก, counter stream์ ๋ง๋ญ์๋ค. counter stream์ ๋ฒํผ์ด ๋ช๋ฒ click๋์๋์ง ๋ํ๋ ๋๋ค. ๋ณดํต์ Reactive ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ๊ฐ๊ฐ์ stream์ ์ฐ๊ฒฐํ ์ ์๋ ๋ง์ ํจ์๋ค์ด ์์ต๋๋ค. map, filter, scan, ๊ธฐํ๋ฑ๋ฑ ๊ฐ์ ๊ฒ๋ค์ด ์์ต๋๋ค. ์ฌ๋ฌ๋ถ์ด ์ด ํจ์๋ค ์ค ํ๋(clickStream.map(f)๊ฐ์ ํจ์)๋ฅผ ํธ์ถํ ๋, ๊ทธ ํจ์๋ ๊ทธ click stream์ ๊ธฐ๋ฐํ ์๋ก์ด stream์ ๋ฆฌํดํฉ๋๋ค. ๊ทธ ํจ์๋ค์ ์ด๋ค ๋ฐฉ์์ผ๋ก ๋ ์ง ๊ฐ์ ์๋ click stream์ ๋ณ๊ฒฝํ์ง ์์ต๋๋ค. ์ด๊ฒ์ ๋ถ๋ณ์ฑ(immutability)๋ผ๊ณ ๋ถ๋ฆฌ์ฐ๋ ์์ฑ์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ถ๋ณ์ฑ์ ํฌ์ผ์ดํฌ์ ์๋ฝ์ด ์ ์ด์ธ๋ฆฌ๋ ๊ฒ์ฒ๋ผ Reactive stream๋ค๊ณผ ํจ๊ป ์ ์ด์ธ๋ฆฝ๋๋ค. ์๋ stream์ ๊ทธ๋๋ก ๋๊ณ ์๋ก์ด stream์ ์์ฑํ๋ ๋ฐฉ์์ 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) ํจ์๋ ์ฌ๋ฌ๋ถ์ด ์ ๊ณตํ f ๋ผ๋ ํจ์์ ๋ฐ๋ผ ๋ฐ์ํ(emitted) ๊ฐ๊ฐ์ ๊ฐ์ ์นํํฉ๋๋ค. ์ฐ๋ฆฌ์ ๊ฒฝ์ฐ์๋ ๊ฐ click ์ ๋ํด์ ์ซ์ 1์ด ์ฌ์(map)๋๋๋ก ํฉ๋๋ค. scan(g) ํจ์๋ stream์ ๋ชจ๋ ์ด์ ์ ๊ฐ๋ค์ ํตํฉํฉ๋๋ค. x = g(accumulated, current) ์ด๋ผ๋ ๊ฐ์ด ์์ฑ๋ฉ๋๋ค. g๋ ์ด ์์ ์์๋ ๊ฐ๋จํ ๋ง์ ํจ์์์ต๋๋ค. ๊ทธ๋ฐ๋ค์ counterStream ์ click ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค click ํ์๊ฐ ๋ฐ์ํฉ๋๋ค.
Reactive ์ ํ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด์, "double click" ์ด๋ฒคํธ๋ค์ stream์ ๊ฐ์ง๊ธฐ๋ฅผ ์ํ๋ค๊ณ ํด๋ด ์๋ค. ๋ ์ฌ๋ฏธ์๊ฒ ํ๊ธฐ ์ํด์, triple click์ double click์ผ๋ก ๊ฐ์ฃผํ๋ ์๋ก์ด stream์ ์ํ๋ค๊ณ ํด๋ด ์๋ค. ํน์ ์ผ๋ฐ์ ์ผ๋ก multiple click๋ค(๋๋ฒํน์ ๊ทธ ์ด์)์ double click์ผ๋ก ๊ฐ์ฃผํ๋ค๊ณ ํด๋ด ์๋ค. ์ฌํธํก์ ํ๊ณ ์ ํต์ ์ธ ๋ช ๋ นํ์ ์ํ๋ฅผ ์ ์งํ๋ ๋ฐฉ์์ผ๋ก ๊ทธ๋ฐ ๊ฒ๋ค์ ์ด๋ป๊ฒ ํด์ผ ํ ์ง ์์ํด๋ณด์ธ์. ๊ทธ๊ฒ์ ๊ฝค ๋ถ์พํ๊ฒ ๋ค๋ฆด ๊ฒ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํ ๋ช๊ฐ์ ๋ณ์์ time interval๋ค์ ์ด์ฉํ ๋ช๊ฐ์ง ์์์๋ฅผ ํฌํจํด์ผ ํฉ๋๋ค.
๊ธ์, Reactive ๋ฐฉ์์ผ๋ก๋ ๊ฝค ๊ฐ๋จํฉ๋๋ค. ์ฌ์ค, ๋ก์ง์ 4์ค์ง๋ฆฌ ๊ฐ๋จํ ์ฝ๋์ ๋๋ค. ํ์ง๋ง ์ง๊ธ์ ์ฝ๋๋ ๋ฌด์ํฉ์๋ค. ์ด์ฌ์์ด๊ฑฐ๋ ์ ๋ฌธ๊ฐ์ด๊ฑฐ๋ ํ ๊ฒ ์์ด, ๋ค์ด์ด๊ทธ๋จ์ผ๋ก ์๊ฐํ๋ ๊ฒ์ด stream๋ค์ ์์ฑํ๊ณ ์ดํดํ๋๋ฐ ์ต์ ์ ๋ฐฉ๋ฒ์ ๋๋ค.
ํ์ ์์๋ค์ ํ๋์ stream์ ๋ค๋ฅธ stream์ผ๋ก ๋ณํ์ํค๋ ํจ์๋ค์ ๋๋ค. ์ฐ์ ์ฐ๋ฆฌ๋ 250 ๋ฐ๋ฆฌ์ธ์ปจ๋ ๋จ์์ "event silence"๊ฐ ๋ฐ์ํ ๋๋ง๋ค ๋ฆฌ์คํธ๋ค์์ click๋ค์ ๋์ฐํฉ๋๋ค. (์ฆ, ํ๋ง๋๋ก buffer(stream.throttle(250ms))์ด ํ๋ ํ์์ ๋๋ค. ์ด ์์ ์์ ์์ธํ ๊ฒ๋ค์ ์ดํดํ ๋ชปํ๋ค๊ณ ๊ฑฑ์ ํ์ง ๋ง์ธ์. ์ฐ๋ฆฌ๋ ์ง๊ธ ๋จ์ง Reactive ๋ฐ๋ชจ๋ฅผ ํ๊ณ ์์ ๋ฟ์ ๋๋ค.) ๊ฒฐ๊ณผ๋ ๋ฆฌ์คํธ์ stream์ ๋๋ค. ๊ทธ stream์ ์ฐ๋ฆฌ๊ฐ map()์ ์ ์ฉ์์ผ์ ๊ฐ๊ฐ์ ๋ฆฌ์คํธ๋ฅผ ๋ฆฌ์คํธ์ ๊ธธ์ด์ ๋งค์นญ๋๋ ์ซ์๋ก ์ฌ์(map)ํ ๊ฒ์ ๋๋ค. ๋ง์ง๋ง์ผ๋ก ์ฐ๋ฆฌ๋ ์ซ์ 1๋ค์ filter(x >= 2)๋ฅผ ์ด์ฉํด์ ๋ฌด์ํฉ๋๋ค. ์๋๋ stream์ ์์ฐํ๊ธฐ ์ํ 3๊ฐ์ง ์ฐ์ฐ๋ค์ด ๊ทธ๊ฒ๋๋ค. ๊ทธ๋ผ ๋ค์, ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๋๋ก ๋ฐ์ํ๋๋ก subscribe("listen")ํ ์ ์์ต๋๋ค.
์ด๋ฐ ๋ฐฉ์์ ์๋ฆ๋ค์์ ์ฆ๊ธฐ์ จ๊ธฐ๋ฅผ ํฌ๋งํฉ๋๋ค. ์ด ์์ ๋ ๋จ์ง ๋น์ฐ์ ์ผ๊ฐ์ผ ๋ฟ์ ๋๋ค. ์ฌ๋ฌ๋ฒ์ ๊ฐ์ ์ฐ์ฐ์ ๋ค๋ฅธ ์ข ๋ฅ์ stream๋ค์ ์ ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด API ์๋ต์ ๋ํ stream ๊ฐ์ ๊ฒ์ด ์์ต๋๋ค. ๋ฐ๋ฉด์ ์ ์ฉํ ์ ์๋ ๋ง์ ๋ค๋ฅธ ํจ์๋ ์์ต๋๋ค.
Reactive Programming์ ์ฌ๋ฌ๋ถ์ ์ฝ๋์ ์ถ์ํ ๋ ๋ฒจ์ ์ฌ๋ ค์ค๋๋ค. ๊ทธ๋์ ์ฌ๋ฌ๋ถ์ ์ง์์ ์ผ๋ก ์๋น๋์ ์์ธ์ฌํญ์ ์์ฑํด์ผ ํ๊ธฐ ๋ณด๋ค๋ ๋น์ง๋์ค ๋ก์ง์ ์ ์ํ๋ ์ด๋ฒคํธ๋ค์ ์ํธ์์กด์ฑ์ ์ง์คํ ์ ์์ต๋๋ค. RP๋ก ์์ฑํ ์ฝ๋๋ ๋ณด๋ค ๋ ๊ฐ๊ฒฐํด์ง ๊ฒ๋๋ค.
์ฅ์ ์ ๋ฐ์ดํฐ ์ด๋ฒคํธ์ ๊ด๋ จ๋ ๋ง์ UI ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํด์ผ ํ๋ ๋ฐ์์ฑ์ด ๋์ ๋ชจ๋ ์น์ฑ์ด๋ ๋ชจ๋ฐ์ผ ์ฑ์์ ๋๋๋ฌ์ง๋๋ค. 10๋ ์ ์, ์น ํ์ด์ง์์ ์ํธ์์ฉ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธด ์์์ ๋ฐฑ์๋๋ก ๋ณด๋ด๊ณ ํ๋ก ํธ์๋์ ๊ทธ๋ฆฌ๋ ๊ฒ์ด์์ต๋๋ค. ์ฑ๋ค์ ์ ์ ๋ ์ค์๊ฐ์ผ๋ก ๋ฐ์ ํด์์ต๋๋ค.: ํ๋์ ์์ field๋ฅผ ์์ ํ๋ ๊ฒ์ ์๋์ผ๋ก ๋ฐฑ์๋์ ์ ์ฅ๋๋๋ก ํ ์ ์์ต๋๋ค. ์ด๋ค ๋ด์ฉ์ ๋ํ "์ข์์"๋ ์ ์๋์ด ์๋ ๋ค๋ฅธ ์ฌ์ฉ์์๊ฒ ์ค์๊ฐ์ผ๋ก ํ์ ๋ ์ ์๋ค๋ ๊ฒ ๋ฑ์ด ์์ต๋๋ค.
์์ฆ ์ฑ๋ค์ ์ฌ์ฉ์์๊ฒ ๋งค์ฐ ๋ฐ์์ ์ธ ๊ฒฝํ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ชจ๋ ์ข ๋ฅ์ ์ค์๊ฐ ์ด๋ฒคํธ๋ฅผ ์์ฃผ ๋ง์ด ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ๊ทธ๋ฐ ๊ฒ๋ค์ ์ ์ ํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ ํด์ด ํ์ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ Reactive Programming์ด ๊ทธ ๋ต์ ๋๋ค.
ํ์ค์ ์ธ ๊ฒ๋ค๋ก ๋ฐ์ด ๋ค์ด ๋ด ์๋ค. RP๋ก ์๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋ํ step-by-step ๊ฐ์ด๋๋ฅผ ํตํ ํ์ค์ธ๊ณ ์์ . ์ต์ง๋ก ๋ง๋ค์ด ๋ธ ์์ ๋ค์ด ์๋๊ณ , ๋ฐ์ฏค ์ค๋ช ํ๋ค๊ฐ ๋ง๋ ๊ฐ๋ ์ด ์๋ ์ง์ง ์์ . ์ด ํํฐ๋ฆฌ์ผ์ ๋์์ ์ฐ๋ฆฌ๋ ๊ฐ๊ฐ์ ๊ฒ๋ค์ ํ๋ ์ด์ ๋ฅผ ์๋ฉด์ ์ง์ง ๋์ํ๋ ์ฝ๋๋ฅผ ๋ง๋ค์ด ๋ผ ๊ฒ๋๋ค.
์ ๋ Javascript ์ RxJS ๋ฅผ ์ด ์์ ๋ฅผ ์ํ ํด๋ก ์ ํํ์ต๋๋ค. ์ด์ ๋ :Javascript๋ ์ง๊ธ ์ด ์๊ฐ ์ธ๊ณ์์ ์ ์ผ ์ธ๊ธฐ์๋ ์ธ์ด์ด๊ณ , Rx* library family ๋ ๋ง์ ์ธ์ด์ ํ๋ซํผ(.NET, Java, Scala, Clojure, Javascript, Ruby, Python, C++, object-C/Cocoa, Groovy, ๊ธฐํ๋ฑ๋ฑ)์์ ํญ ๋๊ฒ ์ฌ์ฉ๊ฐ๋ฅํฉ๋๋ค. ๊ทธ๋์ ์ฌ๋ฌ๋ถ์ ํด์ด ๋ฌด์์ด๋์ง ๊ฐ์, ์ด ํํฐ๋ฆฌ์ผ์ ๋ฐ๋ผํจ์ผ๋ก์จ ๊ตฌ์ฒด์ ์ผ๋ก ๋์์ด ๋ ๊ฒ์ ๋๋ค.
ํธ์ํฐ์๋ ์ฌ๋ฌ๋ถ์ด followํ ๊ณ์ ์ ์ถ์ฒํ๋ ์ด๋ฐ UI ์์๊ฐ ์์ต๋๋ค.
์ฐ๋ฆฌ๋ ๊ทธ๊ฒ์ ์ฃผ ๊ธฐ๋ฅ์ ๋ชจ๋ฐฉํ๋๋ฐ ์ง์คํ ๊ฒ์ ๋๋ค. ์ฃผ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
-์์ํ ๋, API๋ก ๋ถํฐ ๊ณ์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๊ณ ์ถ์ฒ 3๊ฐ์ง๋ฅผ ๋ณด์ฌ ์ค๋๋ค.
-Refresh ๋ฒํผ์ clickํ ๋, 3์ค๋ก ๋ค๋ฅธ 3 ๊ณ์ ์ ์ถ์ฒ์ ๋ก๋ํฉ๋๋ค.
-x ๋ฒํผ์ clickํ ๋, ํ์ฌ ๊ณ์ ์ ์ง์ฐ๊ณ , ๋ค๋ฅธ ๊ณ์ ์ ๋ณด์ฌ์ค๋๋ค.
-๊ฐ ์ค์ ๊ณ์ ์ ์๋ฐํ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ๊ทธ๋ค์ ํ์ด์ง๋ก ์ฐ๊ฒฐ๋์ด ์์ต๋๋ค.
๋ง์ด๋ํ ๋ฒํผ๊ณผ ๊ธฐ๋ฅ์ ๋จ๊ฒจ๋๊ธฐ๋ก ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ต๊ทผ ๋ฏธ์น์ธ๋ ์ผ๋ฐ์ ๋ํด API๋ฅผ ๋ซ์ ํธ์ํฐ ๋์ ์ ๊นํ์ followํ๊ณ ์๋ ์ฌ๋๋ค์ ๋ํ UI๋ฅผ ๋ง๋ญ์๋ค. ์ฌ๊ธฐ ์ ์ ๋ค์ ์ป์ ์ ์๋ ๊นํ API( https://developer.github.com/v3/users/#get-all-users )๊ฐ ์์ต๋๋ค.
๋ฐ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ๋ฅผ ์ํ๋ค๋ฉด, http://jsfiddle.net/staltz/8jFJH/48/ ์ ์์ ํ ์ฝ๋๊ฐ ์์ต๋๋ค.
์ด ๋ฌธ์ ์ ๋ํด์ Rx๋ก ์ด๋ป๊ฒ ์ ๊ทผํ ๊น์? ๊ธ์์. ์ฐ์ , (๊ฑฐ์) ๋ชจ๋ ๊ฒ์ stream์ด ๋ ์ ์์ต๋๋ค. ๊ทธ๊ฑด Rx ์ฃผ๋ฌธ(mantra) ์ ๋๋ค. ๊ฐ์ฅ ์ฌ์ด ๊ธฐ๋ฅ์ผ๋ก ์์ํด ๋ด ์๋ค.:"์์ํ ๋, API๋ก ๋ถํฐ 3๊ฐ์ ๊ณ์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ ํฉ๋๋ค." ๋ฑํ ํน๋ณํ ๊ฒ์ ์์ต๋๋ค. ์ด๊ฒ์ ๊ฐ๋จํ (1) ์์ฒญ(request)๋ฅผ ํ๋ ๊ฒ (2) ์๋ต(response)๋ฅผ ๋ฐ๋ ๊ฒ (3)์๋ต(response)๋ฅผ ๊ทธ๋ฆฌ๋ ๊ฒ ์ ๋ํ ๊ฒ์ ๋๋ค. ๊ทธ๋ผ ์ข ๋ ์์ผ๋ก ๋์๊ฐ ์ฐ๋ฆฌ์ ์์ฒญ(request)๋ค์ stream์ผ๋ก ํํํด ๋ด ์๋ค. ์ฒ์์๋ ๊ณผ๋ํ๊ฒ ๋๊ปด์ง ์ ์์ ๊ฒ๋๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ ๊ธฐ์ด๋ถํฐ ์์ํ๊ธฐ๋ฅผ ์ํฉ๋๋ค. ๊ทธ๋ ์ฃ ?
์์ํ ๋ ์ฐ๋ฆฌ๋ ๋จ์ง ํ๋์ ์์ฒญ(request)์ ๋ณด๋ผ ํ์๊ฐ ์์ต๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๊ฐ ๊ทธ๊ฒ์ data stream ์ผ๋ก ๋ชจ๋ธ๋งํ๋ค๋ฉด, ๊ทธ๊ฒ์ ๋จ์ง ํ๋์ ๋ฐ์ํ(emitted) ๊ฐ์ ๊ฐ์ง๋ stream ์ด ๋ ๊ฒ์ ๋๋ค. ๋์ค์, ์ฐ๋ฆฌ๋ ๋ง์ ์์ฒญ(request)๋ค์ ๊ฐ์ง๊ฒ ๋ ๊ฒ์ ์์ง๋ง ์ง๊ธ์ ๊ทธ๋ฅ ํ๋์ ๋๋ค.
--a------|->
a ๋ 'https://api.github.com/users' ๋ฌธ์์ด์
๋๋ค.
์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ์์ฒญ(request)ํ๊ธฐ ์ํ๋ URL๋ค์ stream ์ ๋๋ค. ์์ฒญ(request)์ด ๋ฐ์ํ ๋ ๋ง๋ค, ์์ฒญ(request)๋ ์ฐ๋ฆฌ์๊ฒ ๋๊ฐ์ง๋ฅผ ์๋ ค์ค๋๋ค.:๋(when) ์ ๋์(what) ย ย ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ (emitted) ๋๊ฐ ์์ฒญ(request)์ด ์คํ๋์ด์ผ ํ ๋(when)์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์์ฒญ๋์ด์ผ ํ ๊ฒ(what)์ ๋ฐ์ํ(emitted) ๊ฐ์ ๋๋ค.:URL ์ ํฌํจํ๋ ๋ฌธ์์ด ย
Rx* ์์ ํ๋์ ๊ฐ์ ๊ฐ์ง๋ ๊ทธ๋ฐ stream ์ ์์ฑํ๋ ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค. stream ์ ๋ํ ๊ณต์ ์ฉ์ด๋ "Observable" ์ ๋๋ค, ๊ด์ฐฐ๋ ์ ์๋ค๋ ์ฌ์ค ๋๋ฌธ์ "Observable" ์ด๋ผ๊ณ ํ๋๋ฐ, ์ ๋ ๊ทธ๊ฒ์ด ๋ฐ๋ณด ๊ฐ์ ์ด๋ฆ์ด๋ผ๋ ๊ฒ์ ์๋๋ค. ๊ทธ๋์ ์ ๋ stream ์ด๋ผ๊ณ ๋ถ๋ฅผ ๊ฒ๋๋ค.
var requestStream = Rx.Observable.just('https://api.github.com/users');
๊ทธ๋ฌ๋ ์ง๊ธ์ ๊ทธ๊ฑด ๋จ์ง ๋ฌธ์์ด๋ค์ stream์ผ ๋ฟ์ ๋๋ค. ์๋ฌด๋ฐ ๋ค๋ฅธ ๋์์ ํ์ง ์์ต๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๋ ๊ฐ์ด ๋ฐ์ํ (emitted) ๋, ์ด์จ๋ ๋ญ๊ฐ ๋ฐ์ํ๊ธฐ๋ฅผ ์ํฉ๋๋ค. ๊ทธ๋ฐ ๊ฒ์ stream ์ subscribe( https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypesubscribeobserver--onnext-onerror-oncompleted ) ํ๋ฉด ๋ฉ๋๋ค.
requestStream.subscribe(function(requestUrl) {
// execute the request
jQuery.getJSON(requestUrl, function(responseData) {
// ...
});
}
์ฐ๋ฆฌ๋ ์์ฒญ(request) ์ฐ์ฐ์ ๋น๋๊ธฐ์ฑ์ ๋ค๋ฃจ๊ธฐ ์ํด์ jQuery Ajax callback(์ฌ๋ฌ๋ถ์ด ์ด๋ฏธ ์๊ณ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.) ์ ์ฌ์ฉํ๊ณ ์๋ค๋ ๊ฒ์ ๋ช ์ฌํ์ธ์. ํ์ง๋ง ์ ์๋ง ๊ธฐ๋ค๋ฆฌ์ธ์. Rx ๋ ๋น๋๊ธฐ data stream ์ ๋ค๋ฃจ๊ธฐ ์ํ ๊ฒ๋๋ค.
๊ทธ ์์ฒญ(request)์ ๋ํ ์๋ต(response)์ ๋ฏธ๋์ ์ด๋ค ์์ ์ ๋์ฐฉํ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ stream ์ด ๋ ์ ์์๊น์? ๊ธ์, ๊ฐ๋ ์ ์ผ๋ก๋, ๊ทธ๋ ๊ฒ ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ๊ทธ๋ผ ๊ทธ๊ฑธ ์๋ ํด๋ด ์๋ค.
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์๊ฒ ์๋ฆผ์ผ๋ก์จ(ํน์ ๋ค๋ฅธ ๋ง๋ก, "subscriber") data ์ด๋ฒคํธ๋ค( onNext() ํน์ ์ค๋ฅ(onError() )์ ๋ํด ์ฌ๋ฌ๋ถ์ custom stream ์ ์์ฑํฉ๋๋ค. ์ฐ๋ฆฌ๊ฐ ํ ๊ฒ์ ๋จ์ง jQuery Ajax Promise ๋ฅผ ๊ฐ์ธ๋ ๊ฒ์ด์์ต๋๋ค. ์ค๋ก์ง๋ง, ์ด๊ฑด Promise ๋ Observable ์ด๋ผ๋ ๋ง์ ๋๊น? (*Promise ๋ ๋ถ๋ณ(immutable) ๊ฐ์ ์บก์ํํ๋ thread-safe ๊ฐ์ฒด๋ก, ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ํ ๊ฒ์ผ๋ก, ์ค์ ๋ก ๊ฐ์ด ์์ฑ๋ ๋๊น์ง ๊ทธ๊ฒ์ ์ฐธ์กฐํ๋ ๋ถ๋ถ์ block๋๊ณ ๊ฐ์ด ์๊ธฐ๋ฉด, block์ด ํด์ ๋ฉ๋๋ค.)
๋ค, ๊ทธ๋ ์ต๋๋ค.
Observable ์ Promise++ ์ ๋๋ค. Rx์์ ์ฌ๋ฌ๋ถ์ var stream = Rx.Observable.fromPromise(promise)๋ฅผ ํจ์ผ๋ก์จ Promise๋ฅผ ์ฝ๊ฒ Observale๋ก ๋ณํํ ์ ์์ต๋๋ค. ๊ทธ๋ผ ๊ทธ๊ฒ์ ์ฌ์ฉํฉ์๋ค. ์ ์ผํ ์ฐจ์ด์ ์ Observale ๋ค์ Promises/A+( https://promisesaplus.com/ ) ์์(complicant)์ด ์๋๋ผ๋ ์ ์ด๋ค. ํ์ง๋ง ๊ฐ๋ ์ ์ผ๋ก ์ถฉ๋์ ์์ต๋๋ค. Promise ๋ ๋จ์ํ ํ๋์ ๋ฐ์ํ(emitted) ๊ฐ์ ๊ฐ์ง๋ Observable ์ ๋๋ค. Rx stream ๋ค์ ๋ง์ ๋ฆฌํด๋ ๊ฐ๋ค์ ํ์ฉํจ์ผ๋ก์จ promise ๋ค์ ๋ฅ๊ฐํฉ๋๋ค.
์ด๊ฑด ๊ฝค ์ข์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ฒ์ Observable๋ค์ด ์ต์ํ Promise๋ค ๋งํผ ๊ฐ๋ ฅํ์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค. ๊ทธ๋์ ๋ง์ฝ ์ฌ๋ฌ๋ถ์ด Promise ์ ์ ๊ด๊ณ ๋ฅผ ๋ฏฟ๋๋ค๋ฉด, Rx Observable๋ค์ด ํ ์ ์๋ ๊ฒ๋ค์ ๋ํด์ ๊ด์ฌ์ ๊ฐ์ ธ๋ณด์ธ์.
์์ ๋ก ๋ค์ ๋์์์, ๋ง์ฝ ์ฌ๋ฌ๋ถ์ด ๊ณง ์์ ์ฑ๋ค๋ฉด, ์ฐ๋ฆฌ๋ ์ฝ๋ฐฑ ์ง์ฅ(callback hell) ๊ฐ์ ๋ค๋ฅธ subscribe() ์์ ํ๋์ subscribe() ํธ์ถ์ ๊ฐ์ง๋๋ค. ๋ํ, responseStream ์ requestStream ์ ์์กด์ ์ ๋๋ค. ์ด์ ์๋ ๋ค์๋ ๊ฒ์ฒ๋ผ, Rx ์๋ stream๋ค๋ก๋ถํฐ ๋ค๋ฅธ stream์ ์๋กญ๊ฒ ์์ฑํ๊ณ ๋ณํํ๋ ๊ฐ๋จํ ๋งค์ปค๋์ฆ์ด ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ฐ๋ฆฌ๋ ๊ทธ๋ ๊ฒ ํด์ผ ํฉ๋๋ค.
์ง๊ธ์ฏค ์ฌ๋ฌ๋ถ์ด ์์์ผ ํ๋ ๊ธฐ๋ณธ ํจ์ ํ๋๊ฐ map(f) ์ ๋๋ค. map(f) ๋ stream A ์ ๊ฐ๊ฐ์ ๊ฐ๋ค์ ์ทจํด์ f()๋ฅผ ์ ์ฉํ๊ณ stream B ์ ๋ํ ๊ฐ์ ์ถ๋ ฅํฉ๋๋ค. ๋ง์ฝ ์ฐ๋ฆฌ๊ฐ map(f)๋ฅผ ์ฐ๋ฆฌ์ ์์ฒญ(request) stream๋ค์ ์ ์ฉํ๋ค๋ฉด, ์์ฒญ(request) URL๋ค์ ์๋ต(response) Promise ๋ค๋ก ์ฌ์ํ (map) ์ ์์ต๋๋ค.( stream ๋ค๋ก ์์ฅ๋ฉ๋๋ค.)
var responseMetastream = requestStream
.map(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
๊ทธ๋ฐ ๋ค์ ์ฐ๋ฆฌ๋ "mainstream" ์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ ์์ฑ๋ ๊ดด๋ฌผ์ ๊ฐ์ง๊ฒ ๋ ๊ฒ๋๋ค.: stream๋ค ์ค์ stream์ ๋๋ค. ์์ง ๋นํฉํ์ง ๋ง์ธ์. mainstream ์ ๊ฐ๊ฐ์ ๋ฐ์ํ(emitted) ๊ฐ์ด ์์ง ๋ค๋ฅธ stream ์ธ stream ์ ๋๋ค. ์ฌ๋ฌ๋ถ์ ๊ทธ๊ฒ์ ํฌ์ธํฐ( https://en.wikipedia.org/wiki/Pointer_(computer_programming) )๋ค๋ก ์๊ฐํ ์ ์์ต๋๋ค. : ๊ฐ๊ฐ์ ๋ฐ์ํ(emitted) ๊ฐ์ ๋ค๋ฅธ stream์ ๋ํ ํฌ์ธํฐ์ ๋๋ค. ์ฐ๋ฆฌ ์์ ์์๋, ๊ฐ๊ฐ์ ์์ฒญ(request) URL ์ ๊ด๊ณ๋ ์๋ต(response)์ ํฌํจํ๋ promise stream ์ ๋ํ ํฌ์ธํฐ๋ก ์ฌ์(map)๋ฉ๋๋ค.
์๋ต(response)๋ค์ ๋ํ mainstream ์ ํผ๋์ค๋ฌ์ ๋ณด์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ์๊ฒ ์ ํ ๋์์ด ๋์ง ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ ๋๋ค. ์ฐ๋ฆฌ๋ ๋จ์ง ์๋ต(response)๋ค์ ๋ํ ๊ฐ๋จํ stream ์ ์ํฉ๋๋ค. ๊ฐ๊ฐ์ ๋ฐ์ํ(emitted) ๊ฐ์ JSON ๊ฐ์ฒด์ด๊ณ , JSON ์ 'Promise'๊ฐ ์๋ stream์ ๋๋ค. Flatmap์จ์๊ฒ ์ธ์ฌํด๋ณด์ธ์. ๊ฐ์ง("branch") stream๋ค์ ๋ฐ์ํ(emitted) ๋ชจ๋ ๊ฒ์ "trunk" stream ์ ๋ฐ์์ํด(emitting)์ผ๋ก์จ mainstream ์ ๋๊ฒ ํด๋("flattens") map์ ํ ๋ฒ์ ์ ๋๋ค. Flatmap์ ์์ ("fix")์ด ์๋๋๋ค. ๊ทธ๋ฆฌ๊ณ mainstream๋ค์ ๋ฒ๊ทธ๊ฐ ์๋๋๋ค. ์ด๋ฐ ๊ฒ๋ค์ Rx์์ ์ฌ์ค์ ๋น๋๊ธฐ ์๋ต๋ค์ ๋ค๋ฃจ๊ธฐ ์ํ ๋๊ตฌ๋ค์ ๋๋ค.
var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
์ข์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ต(response) stream ์ ์์ฒญ(request) stream์ ๋ฐ๋ผ ์ ์๋ฉ๋๋ค. ๋ง์ฝ ๋์ค์ ์์ฒญ(request) stream ์ ๋ ๋ง์ ์ด๋ฒคํธ๊ฐ ์๊ธด๋ค๋ฉด, ๊ธฐ๋ํ๋ ๋๋ก, ์๋ต(response) stream์ ๋ฐ์ํ๋ ๊ด๋ จ๋ ์๋ต(response) ์ด๋ฒคํธ๋ฅผ ๊ฐ์ง๊ฒ ๋ ๊ฒ๋๋ค.
requestStream: --a-----b--c------------|->
responseStream: -----A--------B-----C---|->
(์๋ฌธ์๋ ์์ฒญ(request)์ด๊ณ , ๋๋ฌธ์๋ ๊ทธ๊ฒ์ ์๋ต(response)์
๋๋ค.)
์ด์ ์ฐ๋ฆฌ๋ ๋ง์นจ๋ด ์๋ต(response) stream์ ๊ฐ์ง๊ฒ ๋์๊ณ , ์ฐ๋ฆฌ๊ฐ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋ฆด ์ ์์ต๋๋ค.
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๋ ์ฐ๋ฆฌ๊ฐ ํ์ด์ง ์คํ์ ๋ฅผ ์ง์ ํ๋ ๊ฒ์ ํ์ฉํ๊ณ , ํ์ด์ง ์ฌ์ด์ฆ๋ฅผ ์ง์ ํ๋ ๊ฒ์ ํ์ฉํ์ง ์์ต๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๋ 3๊ฐ์ data object๋ค์ ์ฌ์ฉํ๊ณ 97๊ฐ์ ๋ฐ์ดํฐ๋ ๋ฒ๋ฆฝ๋๋ค. ๋์ค์ ์๋ต(response)๋ค์ cacheํ๋ ๋ฐฉ๋ฒ์ ์์๋ณผ ๊ฒ์ด๊ธฐ ๋๋ฌธ์,์ง๊ธ์ ๊ทธ ๋ฌธ์ ๋ ๋ฌด์ํฉ๋๋ค.
refresh button ์ด click๋ ๋๋ง๋ค, ์์ฒญ(request) stream์ ์๋ก์ด URL์ ๋ฐ์์์ผ์ผ(emit) ํ๊ณ ๊ทธ๋ ๊ฒ ํด์ ์๋ก์ด ์๋ต(response)๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ๋๊ฐ์ง๊ฐ ํ์ํฉ๋๋ค.: click ์ด๋ฒคํธ๋ค์ stream(์ฃผ๋ฌธ(mantra): ์ด๋ค ๊ฒ์ด๋ ๊ฐ์ stream์ด ๋ ์ ์์ต๋๋ค.) ๊ทธ๋ฆฌ๊ณ ์๋ต(request) stream์ด refresh click stream์ ์์กดํ๋๋ก ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค. ๊ธฐ์๊ฒ๋, RxJS์๋ Observables๋ฅผ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ค๋ก ๋ง๋ค๊ธฐ ์ํ ๋๊ตฌ๋ค์ด ๋ธ๋ ค ์์ต๋๋ค.
var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
refresh click ์ด๋ฒคํธ ๊ทธ ์์ฒด๋ก๋ ์ด๋ค API URL์ ์ด๋ฐํ์ง ์๊ธฐ ๋๋ฌธ์, ์ฐ๋ฆฌ๋ ๊ฐ๊ฐ์ click ์ ์ค์ URL ๋ก ์ฌ์(map)ํ ํ์๊ฐ ์์ต๋๋ค. ์ด์ ์ฐ๋ฆฌ๋ ์์ฒญ(request) stream๋ฅผ ๋งค๋ฒ ๋๋ค ์คํ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ง๋ API endpoint ์ ์ฌ์(map)๋ refresh click stream์ผ๋ก ๋ณํํฉ๋๋ค.
var requestStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
์ ๋ ์๋ํ๊ณ , ์๋ํ๋ ํ ์คํธ๋ฅผ ๊ฐ์ง๊ณ ์์ง ์๊ธฐ ๋๋ฌธ์, ์ด์ ์ ๋ง๋ ๊ธฐ๋ฅ์ค ํ๋๋ฅผ ๋ง๊ฐ๋จ๋ ธ์ต๋๋ค. ์์ํ ๋ ์์ฒญ(request)๋ ๋์ด์ ์๋ฌด๊ฒ๋ ๋ฐ์์ํค์ง ์์ต๋๋ค. refresh ๊ฐ click ๋ ๋๋ง ๋ฐ์ํฉ๋๋ค. ์์, ์ ๋ ๋ ๊ฒฝ์ฐ ๋ชจ๋ ๋์์ด ํ์ํฉ๋๋ค.: refresh ๊ฐ click ๋๊ฑฐ๋ ์นํ์ด์ง๊ฐ ์ด๋ฆด ๋ ์์ฒญ(request)
์ฐ๋ฆฌ๋ ๋ค๋ฅธ ๊ฒฝ์ฐ์ ๊ฐ๊ฐ์ stream ์ํ ๊ฐ๋ณ stream ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์๊ณ ์์ต๋๋ค.:
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');
ํ์ง๋ง ์ด๋ป๊ฒ ์ด๋ค ๋๊ฐ์ง stream์ ํ๋์ stream ์ผ๋ก ํฉ์น ์ ์์๊น์? ๊ธ์, 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
);
intermediate stream๋ค์ ์ ๊ฑฐํ ๋ ๊น๋ํ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ์์ฑํ ๊ฒ์ด ์ฌ๊ธฐ ์์ต๋๋ค.
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 ํจ์๋ ์ฐ๋ฆฌ๊ฐ ์๊ฐํ๋ ๊ทธ๊ฒ์ด ์ ํํ ๋ง์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ์ ๋ ฅ stream ์ด ์ด๋ป๊ฒ ์๊ฒผ๋์ง ์๊ด์์ด, startWith(x)์ ๊ฒฐ๊ณผ๋ก ๋ง๋ค์ด์ง๋ output stream์ ์ฒ์์ x ๋ฅผ ๊ฐ์ง๊ฒ ๋ ๊ฒ๋๋ค. ํ์ง๋ง, ์ ๋ ์ถฉ๋ถํ DRY( https://en.wikipedia.org/wiki/Don't_repeat_yourself )๊ฐ ์๋๊ธฐ ๋๋ฌธ์, API endpoint ๋ฌธ์์ด์ ๋ฐ๋ณตํ๊ณ ์์ต๋๋ค. ์ด๊ฒ์ ๊ณ ์น๊ธฐ ์ํ ํ ๋ฐฉ๋ฒ์ ๊ทผ๋ณธ์ ์ผ๋ก ์์์์ refresh click์ "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;
});
์ข์ต๋๋ค. ์ฌ๋ฌ๋ถ์ด "์ ๋ ์๋ํ๋ ํ ์คํธ๋ฅผ ๋ง๊ฐ๋จ๋ ธ์ต๋๋ค."์ ์ง์ ์ผ๋ก ๋์๊ฐ๋ค๋ฉด, ์ฌ๋ฌ๋ถ์ ์ด ๋ง์ง๋ง ์ ๊ทผ ๋ฐฉ์์ด ๋จ์ง ์ ๊ฐ startWith()๋ฅผ ์ถ๊ฐํ๋ ๊ฒ๋ง์ด ์ฐจ์ด๊ฐ ๋๋ค๋ ๊ฒ์ ์์ ์์ด์ผ ํฉ๋๋ค.
์ง๊ธ๊น์ง, ์ฐ๋ฆฌ๋ responseStream์ subscribe()์์ ๋ฐ์ํ๋ ๊ทธ๋ฆฌ๋ ๋จ๊ณ์ ์ถ์ฒ UI element ๋ฅผ ๋ค๋ค์์ต๋๋ค. ์ด์ ๋ refresh button๊ณผ ๊ด๋ จํด์ ๋ฌธ์ ๊ฐ ์๊ฐ ์์ต๋๋ค.:์ฌ๋ฌ๋ถ์ด 'refresh'๋ฅผ ํด๋ฆญํ์ง ๋ง์, ํ์ฌ์ 3๊ฐ์ง ์ถ์ฒ์ด ์ง์์ง์ง ์์ต๋๋ค. ์๋ต(response)์ด ๋์ฐฉํ ์ดํ์ ์๋ก์ด ์ถ์ฒ์ด ๋ค์ด ์ต๋๋ค. ํ์ง๋ง UI๋ฅผ ๋ฉ์ง๊ฒ ๋ง๋ค๊ธฐ ์ํด์, ์ฐ๋ฆฌ๋ refresh ๊ฐ click ๋ ๋ ํ์ฌ ์ถ์ฒ๋ค์ ๋ชจ๋ ์ง์ธ ํ์๊ฐ ์์ต๋๋ค.
refreshClickStream.subscribe(function() {
// clear the 3 suggestion DOM elements
});
์๋์, ์ฌ๋ฌ๋ถ ๋๋ฌด ๋น ๋ฅด์ง๋ ์์ต๋๋ค. ์ด๊ฒ์ ์ข์ง ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ถ์ฒ DOM ์์๋ค์ ์ํฅ์ ์ฃผ๋(ํ๋๋ responseStream.subscribe()) ๋ ๊ฐ์ subscriber๋ค์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ Seperation of concerns( https://en.wikipedia.org/wiki/Separation_of_concerns ) ์ฒ๋ผ ๋ค๋ฆฌ์ง ์์ต๋๋ค. Reactive ์ฃผ๋ฌธ(mantra)๋ฅผ ๊ธฐ์ตํ์๋์?
๊ทธ๋ผ ์ถ์ฒ์ stream์ผ๋ก ๋ชจ๋ธ๋ง ํฉ์๋ค. ์ด stream์์ ๊ฐ๊ฐ์ ๋ฐ์ํ(emitted) ๊ฐ์ ์ถ์ฒ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ JSON ๊ฐ์ฒด์ ๋๋ค. ์ฐ๋ฆฌ๋ ๊ฐ๊ฐ 3๊ฐ์ ์ถ์ฒ์ ๋ํด์ ๊ฐ๋ณ์ ์ผ๋ก ์ด๊ฒ์ ํ ๊ฒ๋๋ค. ์ถ์ฒ #1 ์ ๋ํ stream์ด ์ด๋ ํด์ผ ํ๋์ง๋ฅผ ์ด๊ฒ์ด ๋ณด์ฌ์ค๋๋ค.
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() ์์ ๊ทธ๋ฆฌ๋ ๊ฒ(rendering)์ด ๋ฐ์ํ๋ ๋์ ์ ์ฐ๋ฆฌ๋ ์ฌ๊ธฐ์ ๋ฐ์์ํต๋๋ค.
suggestion1Stream.subscribe(function(suggestion) {
// render the 1st suggestion to the DOM
});
'๋ฆฌํ๋ ์ฌ ๋ ๋, ์ถ์ฒ์ ์ง์ด๋ค'๋ก ๋์์์, ์ฐ๋ฆฌ๋ ๊ฐ๋จํ refresh click ์ null ์ถ์ฒ ๋ฐ์ดํฐ๋ก ์ฌ์(map)ํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ suggestion1Stream ์ ์ด๋ ๊ฒ ๊ทธ๊ฑธ ํฌํจ์ํต๋๋ค.
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; })
);
๊ทธ๋ฆฌ๊ณ ๊ทธ๋ฆด(rendering) ๋, ์ฐ๋ฆฌ๋ null "๋ฐ์ดํฐ ์์"("no data")์ผ๋ก ํด์ํฉ๋๋ค. ๊ฒ๋ค์ฌ 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 ์ null ์ ์๋ฏธํฉ๋๋ค.
๋ณด๋์ค๋ก, ์ฐ๋ฆฌ๋ ์์์ "empty" ์ถ์ฒ์ ๊ทธ๋ฆด ์ ์์ต๋๋ค. ๊ทธ๊ฒ์ ์ถ์ฒ stream ์ 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' ๋ฒํผ์ ๊ฐ์ ธ์ผ ํฉ๋๋ค. ์ฒ์ ์๊ฐํ์ ๋, ์ฌ๋ฌ๋ถ์ ๋ซ๊ธฐ ๋ฒํผ์ด click ๋์์ ๋ ์๋ก์ด ์์ฒญ(request)๋ฅผ ๋ง๋๋ ๊ฒ์ผ๋ก ์ถฉ๋ถํ๋ค๊ณ ๋งํ ์ ์์์ต๋๋ค.:
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;
});
๊ทธ๋ฐ๋ฐ ๊ทธ๊ฒ์ ์ ๋๋ก ์๋ํ์ง ์์ต๋๋ค. ๊ทธ๊ฒ์ ๋ซํ๊ณ , clickํ ํ๋๋ง ๋ค์ ๋ก๋๋ ๊ฒ์ด ์๋๋ผ, ๋ชจ๋ ์ถ์ฒ์ ๋ค์ ๋ก๋ ํฉ๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํธ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋ช๊ฐ์ง ์๋๋ฐ, ์ฌ๋ฏธ๋ฅผ ์ ์งํ๊ธฐ ์ํด์, ์ฐ๋ฆฌ๋ ์ด์ ์๋ต(response)๋ฅผ ์ฌ์ฌ์ฉํด์ ์ด๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ๊ฒ์ ๋๋ค. API ์๋ต ํ์ด์ง ํฌ๊ธฐ๋ 100๋ช ์ ์ ์ ์ ๋๋ค. ๋ฐ๋ฉด์ ์ฐ๋ฆฌ๋ ๊ทธ์ค์์ 3๋ช ๋ง ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋์ ์๋น๋์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค. ๋ ์์ฒญํ ํ์๊ฐ ์์ต๋๋ค.
๋ค์, stream๋ค์ ์๊ฐํด๋ด ์๋ค. 'close1' click ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋, ์ฐ๋ฆฌ๋ ์๋ต(response) ๋ฆฌ์คํธ์์ ๋๋ค ์ฌ์ฉ์ ํ๋๋ฅผ ์ป๊ธฐ ์ํด ๊ฐ์ฅ ์ต๊ทผ์ responseStream ์ ๋ฐ์ํ(emitted) ์๋ต(response)์ ์ฌ์ฉํ๊ธฐ๋ฅผ ์ํฉ๋๋ค.
requestStream: --r--------------->
responseStream: ------R----------->
close1ClickStream: ------------c----->
suggestion1Stream: ------s-----s----->
Rx ์๋ combineLatest ๋ผ๊ณ ๋ถ๋ฆฌ๋ ์ฐ๋ฆฌ๊ฐ ํ์ํ ๊ฒ์ ํด์ฃผ๋ ๊ฒ์ผ๋ก ๋ณด์ด๋ ๊ฒฐํฉ ํจ์๊ฐ ์์ต๋๋ค. ๊ทธ๊ฒ์ ๋๊ฐ์ stream A, B๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋์ค ํ๋์ stream ์ด ๊ฐ์ ๋ฐ์์ํฌ(emit) ๋ ๋ง๋ค, combineLatest ๋ ๋ stream ๋ค๋ก ๋ถํฐ ๊ฐ์ฅ ์ต๊ทผ์ ๋ฐ์ํ(emitted) ๋๊ฐ์ ๊ฐ a, b ๋ฅผ ํฉ์นฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ c = f(x, y) ๊ฐ์ ์ถ๋ ฅํฉ๋๋ค. f๋ ์ฐ๋ฆฌ๊ฐ ์ ์ํ ํจ์์ ๋๋ค. ๋ค์ด์ด ๊ทธ๋จ์ผ๋ก ์ค๋ช ํ๋ ๊ฒ์ด ๋ ์ข๊ฒ ์ต๋๋ค.
stream A: --a-----------e--------i-------->
stream B: -----b----c--------d-------q---->
vvvvvvvv combineLatest(f) vvvvvvv
----AB---AC--EC---ED--ID--IQ---->
where f is the uppercase function
์ฐ๋ฆฌ๋ close1ClickStream ๊ณผ responseStream ์ combineLatest() ๋ฅผ ์ ์ฉํ ์ ์์ต๋๋ค. ๊ทธ๋ ๊ฒ ํด์ close1 ๋ฒํผ์ด click๋ ๋๋ง๋ค, ์ฐ๋ฆฌ๋ ๋ฐ์ํ(emitted) ์ต์ ์๋ต(response)๋ฅผ ์ป๊ณ , suggestion1Stream ์ ์๋ก์ด ๊ฐ์ ์์ฐํฉ๋๋ค. ๋ฐ๋ฉด์, combineLatest()๋ ๋์นญ(symmetric)์ ๋๋ค.: responseStream ์ ์๋ก์ด ์๋ต(response)๊ฐ ๋ฐ์ํ (emitted) ๋๋ง๋ค, ๊ทธ ์๋ต(response)๋ ๊ฐ์ฅ ์ต์ ์ 'close 1' click ๊ณผ ๊ฒฐํฉ๋์ด ์๋ก์ด ์ถ์ฒ์ ์์ฐํฉ๋๋ค.
sugestion1Stream์ ๋ํ ์ฐ๋ฆฌ์ ์ด์ ์ฝ๋๋ฅผ ์ด์ ๊ฐ์ด ๊ฐ๋จํ๊ฒ ๋ง๋ค ์ ์๋๋ก ํด์ฃผ๊ธฐ ๋๋ฌธ์ combineLatest() ๋ ํฅ๋ฏธ๋กญ์ต๋๋ค.
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()๋ ๋ source๋ค๋ก ๋ถํฐ ๊ฐ์ฅ ์ต์ ์ ๊ฒ๋ค์ ์ฌ์ฉํฉ๋๋ค. ํ์ง๋ง ๊ทธ ๋๊ฐ์ source๋ค ์ค์์ ํ๋๊ฐ ์๋ฌด๊ฒ๋ ๋ฐ์ํ์ง(emitted) ์์๋ค๋ฉด, combineLatest() ๋ output stream ์ data ์ด๋ฒคํธ๋ฅผ ์์ฐํ ์ ์์ต๋๋ค. ์ฌ๋ฌ๋ถ์ด ์์ ASCII ๋ค์ด์ด ๊ทธ๋จ์ ๋ณธ๋ค๋ฉด, ์ฒซ stream์ด a ๋ฅผ ๋ฐ์์์ผฐ(emit)์๋ ์๋ฌด๊ฒ๋ ์ถ๋ ฅ๋์ง ์์์์ ์ ์ ์์ ๊ฒ๋๋ค. ๋๋ฒ์งธ stream์ด ๊ฐ b ๋ฅผ ๋ฐ์์ํฌ(emit) ๋, ์ถ๋ ฅ๊ฐ์ ์์ฐํ ๋ ์ ์์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ค๋ฅธ ๋ฐฉ๋ฒ๋ค์ด ์กด์ฌํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๋ ์์ํ ๋ 'close1' ๋ฒํผ์ ๋ํ click์ ํ๋ด๋ด๋ ๊ฐ์ฅ ๊ฐ๋จํ ํด๊ฒฐ์ฑ ์ผ๋ก ๋จธ๋ฌด๋ฅผ ๊ฒ๋๋ค.
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/ )์์ ๋ณผ ์ ์์ต๋๋ค.
์ ์ฝ๋ ์กฐ์์ ์๊ณ ๊ฒฌ๊ณ ํฉ๋๋ค.: ์ ์ฝ๋๋ค์ ์ ์ ํ ๊ณ ๋ ค์ ๋ถ๋ฆฌ(seperation of concerns)๋ก ์ฌ๋ฌ ์ด๋ฒคํธ๋ค์ ๊ด๋ฆฌํ๋ ๊ฒ๊ณผ
๊ฐ๊ฐ์ ์๋ต(response)๋ค์ ์บ์ฌํ๋ ๊ฒ์ผ๋ก ํน์ง์ ์ด๋ฃน๋๋ค. functional ์คํ์ผ์ ์ข ๋ ๋ช
๋ นํ๋ณด๋ค๋ ์ ์ธ์ ์ผ๋ก ๋ณด์ด๊ฒ ํฉ๋๋ค.:
์ฐ๋ฆฌ๋ ์คํํ ๋ช
๋ น์ ์์๋ฐฐ์ด์ ์ฃผ์ง ์๊ณ , stream๋ค๊ฐ์ ๊ด๊ณ๋ฅผ ์ ์ํจ์ผ๋ก์จ ์ด๋ค ๊ฒ์ด ๋ฌด์์ธ์ง๋ฅผ ์ด์ผ๊ธฐํ๊ณ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ฐ๋ฆฌ๋ Rx๋ฅผ ๊ฐ์ง๊ณ ์ปดํจํฐ์๊ฒ suggestion1Stream ์ ์ต์ ์๋ต(response)์ผ๋ก ๋ถํฐ ํ๋ช
์ ์ฌ์ฉ์์ ๊ฒฐํฉ์ํจ 'close 1' stream ์ด๋ผ๊ณ ์ด์ผ๊ธฐ ํด์ฃผ์์ต๋๋ค.
๊ฒ๋ค๊ฐ ๊ทธ ์ต์ ์๋ต(response)์ ํ๋ก๊ทธ๋จ์ด ์์ํ๊ฑฐ๋ refresh ๊ฐ ๋ฐ์ํ ๋ null ์ด ๋๋ค๊ณ ์ด์ผ๊ธฐ ํด์ฃผ์์ต๋๋ค.
๊ฒ๋ค๊ฐ if, for, while ๊ฐ์ ๋ช ๋ นํ control flow ์์์ ์ฌ๋ฌ๋ถ์ด JavaScript ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ธฐ๋ํ๋ ์ ํ์ ์ธ callback-based control flow ๋ ์๋ค๋ ๊ฒ์ ์์ ๋์ธ์. ์ฌ์ง์ด ์ ์ฝ๋์ subscribe() ์์ ์๋ if ์ else ๋ ์ฌ๋ฌ๋ถ์ด ์ํ๋ค๋ฉด, filter() ๋ฅผ ์ฌ์ฉํด์ ์ ๊ฑฐํ ์ ์์ต๋๋ค. (๋ํ ์ผํ ๊ตฌํ์ ์ฌ๋ฌ๋ถ์๊ฒ ์ฐ์ต์ผ๋ก ๋จ๊ฒจ๋๊ฒ ์ต๋๋ค.) Rx ์๋ map, filter, scan, merge, combineLatest, startWith ๊ทธ๋ฆฌ๊ณ ํ๋ฆ์ ์ ์ดํ๊ธฐ(control the flow) ์ํ ๋ ๋ง์ ํจ์๋ค์ด ์์ต๋๋ค. ํจ์ ๋๊ตฌ ๋ชจ์์ ์ฌ๋ฌ๋ฒ์๊ฒ ๋ ์์ ์ฝ๋๋ก ๋ ํฐ ํ์ ์ค๋๋ค.
๋ง์ฝ ์ฌ๋ฌ๋ถ์ด Reactive Programming ์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก Rx* ๋ฅผ ์๊ฐํ๊ณ ์๋ค๋ฉด, tranform, combine, Observable๋ค ์์ฑ์ ๋ํ ๋ง์ ํจ์( https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md ) ์ ์ต์ํด์ง๊ธฐ ์ํด์ ์๊ฐ์ ๋ด์ธ์. ๋ง์ฝ ์ฌ๋ฌ๋ถ์ด ์ด๋ค ํจ์๋ฅผ stream ๋ค์ด์ด๊ทธ๋จ์ผ๋ก ์ดํดํ๊ณ ์ถ๋ค๋ฉด, 'RxJava's very useful documentation with marble diagrams'( https://github.com/ReactiveX/RxJava/wiki/Creating-Observables )๋ฅผ ๋ณด์ธ์. ๋ญ๊ฐ๋ฅผ ํ๋ ค๊ณ ์๋ํ๋๋ฐ ์ด๋ ค์์ ๊ฒช์ ๋๋ง๋ค, ๋ค์ด์ด ๊ทธ๋จ์ ๊ทธ๋ฆฌ๊ณ ๋ค์ด์ด๊ทธ๋จ์ ๊ธฐ๋ฐ์ผ๋ก ์๊ฐํ๊ณ , ํจ์๋ค์ ๊ธด ๋ฆฌ์คํธ๋ฅผ ๋ณด๊ณ , ๊ทธ๋ฆฌ๊ณ ๋ค์ ํ๋ฒ๋ ์๊ฐํด ๋ณด์ธ์. ์ด๋ฐ ์์ ํ๋ฆ์ด ์ ๊ฒฝํ์ ํจ๊ณผ์ ์ด์์ต๋๋ค.
Rx* ๋ฅผ ์ด์ฉํ ํ๋ก๊ทธ๋๋ฐ์ ์ฌ์ฉ๋ฒ์ ์์ํ๋ฉด, Cold vs Hot Observales ( https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables )์ ๋ํ ๊ฐ๋ ์ ์ดํดํ๋ ๊ฒ์ด ์ ๋์ ์ผ๋ก ์๊ตฌ๋ฉ๋๋ค. ๋ง์ฝ ์ฌ๋ฌ๋ถ์ด ์ด๊ฑธ ๋ฌด์ํ๋ค๋ฉด ๋์ค์ ํํํ๊ฒ ๋ ๊ฒ๋๋ค. ๊ฒฝ๊ณ ํ์ต๋๋ค. ์ค์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ๊ณต๋ถํ๊ณ Rx* ์ ์ํฅ์ ๋ฏธ์น๋ Side effect๋ค๊ณผ ๊ฐ์ ์ด์์ ์น์ ํด์ง์ผ๋ก์จ ์ฌ๋ฌ๋ถ์ ๊ธฐ์ ์ ๋ ๊ฐ๊ณ ๋ฆ์ผ์ธ์.
ํ์ง๋ง, Reactive Programming์ ๋จ์ํ Rx๊ฐ ์๋๋๋ค. ๋๋๋ก Rx ์์ ๋ง๋ฅ๋ฐ๋ฆฌ๊ฒ ๋๋ ๋ณ๋์์ด ์์ ํ๊ธฐ์ ์ง๊ด์ ์ธ Bacon.js( http://baconjs.github.io/ )๋ ์์ต๋๋ค. Elm Language( http://elm-lang.org/ )๋ ๊ทธ ์์ ์ ์นดํ ๊ณ ๋ฆฌ์ ์กด์ฌํฉ๋๋ค.: Functional Reactive Programming ์ธ์ด๋ฅผ Javascript + HTML + CSS ๋ก ์ปดํ์ผํฉ๋๋ค. time travelling debugger( http://debug.elm-lang.org/ )๋ก ํน์ง๋ฉ๋๋ค. ๊ฝค ๊ด์ฐฎ์ต๋๋ค.
Rx ๋ event-heavy frontends ์ ์ฑ๋ค์ ๋ํด ํ๋ฅญํ๊ฒ ๋์ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฐ ๊ฑด ๋จ์ง ํด๋ผ์ด์ธํธ ์ฌ์ดํธ๋ง์ ๊ฒ์ด ์๋๋๋ค. ๋ฐฑ์๋์์๋ ํ๋ฅญํ๊ฒ ๋์ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฐ์ดํฐ ๋ฒ ์ด์ค ๊ทผ์ฒ์์๋์. ์ฌ์ค RxJava ๋ Netflix์ API์์ ์๋ฒ ์ฌ์ด๋ ๋์์ฑ ์ฒ๋ฆฌ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ ํต์ฌ ์ปดํฌ๋ํธ์ ๋๋ค.( http://techblog.netflix.com/2013/02/rxjava-netflix-api.html ) Rx ๋ ํน์ ์ธ์ดํน์ ํ๋์ ์ง์ ๋ ํ์ ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตญํ๋ framework๊ฐ ์๋๋๋ค. ๊ทธ๊ฒ์ ์ฌ์ค event-driven ์ํํธ์จ์ด๋ฅผ ํ๋ก๊ทธ๋๋ฐํ ๋ ์ฌ๋ฌ๋ถ์ด ์ฌ์ฉํ ์ ์๋ ํจ๋ฌ๋ค์์ ๋๋ค.
์ด ํํฐ๋ฆฌ์ผ์ด ์ฌ๋ฌ๋ถ์๊ฒ ๋์์ด ๋์๋ค๋ฉด, ํธ์ํด์ฃผ์ธ์.( https://twitter.com/intent/tweet?original_referer=https%3A%2F%2Fgist.github.com%2Fstaltz%2F868e7e9bc2a7b8c1f754%2F&text=The%20introduction%20to%20Reactive%20Programming%20you%27ve%20been%20missing&tw_p=tweetbutton&url=https%3A%2F%2Fgist.github.com%2Fstaltz%2F868e7e9bc2a7b8c1f754&via=andrestaltz )