Advertisement
Advertisement

More Related Content

Advertisement

Compose Async with RxJS

  1. Compose Async with RxJS @chitacan, Riiid 1
  2. https://goo.gl/W1YChu 2 JSBin for example
  3. RxJS ? Reactive extensions library for JavaScript 3
  4. 시작하기 전에 [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 4
  5. 시작하기 전에 [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 4 > 6 > 8
  6. Event 를 이렇게 처리한다면 어떨까요? 5
  7. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 6
  8. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 7
  9. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 8
  10. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 9
  11. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 10
  12. Array 와 Event 는 모두 Collections 11
  13. Event 는 데이터의 모음이고, Array 처럼 처리할 수 있다. 12
  14. Array 와 Event • 차이점 - array 는 각 요소를 동기적 (sync) 으로 조회할 수 있고, 끝이 있다. - event 는 각 요소를 비동기적 (async) 으로 조회할 수 있고, 끝이 없다.
 (하지만 event listening 을 취소 할 수 있다.) • 비동기적으로 생성되는 요소들을 표현하고, 원하는 시점에 취소할 수 있는 타입 13
  15. Observable 14
  16. [ ] time collections over time 15
  17. [ ] time collections over time 15 1....2..3..
  18. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16
  19. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3
  20. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1
  21. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2
  22. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ...
  23. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ... pull
  24. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ... pull push
  25. “RxJS 는 Observable 타입을 활용해 Event를 Array 처럼 처리 할 수 있는 라이브러리” 17
  26. ­Ben Lesh, RxJS In-Depth @AngularConnect 2015 “RxJS is LoDash or Underscore for async” 18
  27. RxJS • Observable 타입 • Operators (map, filter ...) • Scheduler 19
  28. Observable vs Promise Promise Observable single value multiple value not lazy lazy not cancelable cancelable no completion callback completion callback 20
  29. single value vs multiple value • DOM / Event Emitter events (0 - N values) • Animations (cancelable) • REST API (1 value) • WebSockets (0 - N values, retry) • node.js core API (1 - N values) 21
  30. single value vs multiple value • DOM / Event Emitter events (0 - N values) • Animations (cancelable) • REST API (1 value) • WebSockets (0 - N values) • node.js core API (1 - N values) 22
  31. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 23
  32. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 24 executor
  33. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 25 https://goo.gl/tK39aS
  34. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 25 > "started" https://goo.gl/tK39aS
  35. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); Observable: lazy 26
  36. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); Observable: lazy 26 // no console output
  37. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); source.subscribe(val => console.log(val)); Observable: lazy 27 https://goo.gl/qh24FK
  38. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); source.subscribe(val => console.log(val)); Observable: lazy 27 > "started" https://goo.gl/qh24FK
  39. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); source.subscribe(val => console.log(val)); Observable: lazy 27 > "started" > "run" https://goo.gl/qh24FK
  40. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28
  41. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28 > "started"
  42. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28 > "started" > "val: 0"
  43. Promise: not cancelable const p = new Promise(resolve => { var val = 0; setInterval(() => { console.log(`val: ${val}`) resolve(++val); }, 1000); console.log('started'); }); p.then(val => console.log(`result: ${val}`)); 28 > "started" > "val: 0" > "result: 0” // promise result > "val: 1" > "val: 2" > "val: 3" // ??????????
  44. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); 29 https://goo.gl/qJ1zRi
  45. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); > "started" > "val: 0" > "result: 0" > "val: 1" > "result: 1" // ... 30 https://goo.gl/qJ1zRi
  46. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); > "started" > "val: 0" > "result: 0" > "val: 1" > "result: 1" // ... 30 teardown logic https://goo.gl/qJ1zRi
  47. Observable: cancelable const source = Rx.Observable.create(observer => { var val = 0; const id = setInterval(() => { console.log(`val: ${val}`) observer.next(++val); }, 1000); console.log('started'); return () => { clearInterval(id); console.log('cancelled'); }; }); const subscription = source.subscribe(val => console.log(`result: ${val}`)); setTimeout(() => subscription.unsubscribe(), 5000); > "started" > "val: 0" > "result: 0" > "val: 1" > "result: 1" // ... 30 > "cancelled" teardown logic https://goo.gl/qJ1zRi
  48. promise.then(valueFn, errorFn); Completion 31
  49. Completion 32 observable.subscribe(nextFn, errorFn, completeFn);
  50. Operators • observable 타입을 변환 / 조합할 수 있음 • RxJS@5 기준 +200개 • 종류 - creation - transformation - filtering - composition - error - ... 33
  51. creation • create • from • fromEvent • fromPromise • interval 34
  52. from const source = Rx.Observable.from([1,2,3,4,5]) source.subscribe(val => console.log(val)); > 1 > 2 > 3 > 4 > 5 35
  53. Array function vs RxJS const source = [1,2,3,4,5]; const result = source .filter(d => d % 2 === 0) .map(d => d + '!') .reduce((p, d) => p + d) console.log(result); > "2!4!" 36
  54. Array function vs RxJS const source =[1,2,3,4,5]; const result = source .filter((d, i, arr) => { console.log(`filter: ${d}`); return d % 2 === 0; }) .map((d, i, arr) => { console.log(`map: ${d}`); return d + '!'; }) .reduce((p, d, i, arr) => { console.log(`reduce: ${d}`); return p + d; }) console.log(result); > "filter: 1" > "filter: 2" > "filter: 3" > "filter: 4" > "filter: 5" > "map: 2" > "map: 4" > "reduce: 2!" > "reduce: 4!" > "2!4!" 37 https://goo.gl/ma2vxb
  55. Array function vs RxJS const source = Rx.Observable.from[1,2,3,4,5]; source .filter(d => d % 2 === 0) .map(d => d + '!') .reduce((p, d) => p + d) .subscribe(result => console.log(result)); > "2!4!" 38
  56. Array function vs RxJS const source = Rx.Observable.from([1,2,3,4,5]); source .filter(d => { console.log(`filter: ${d}`); return d % 2 === 0; }) .map(d => { console.log(`map: ${d}`); return d + '!'; }) .reduce((p, d) => { console.log(`reduce: ${d}`); return p + d; }, '') .subscribe(result => console.log(result)); > "filter: 1" > "filter: 2" > "map: 2" > "reduce: 2!" > "filter: 3" > "filter: 4" > "map: 4" > "reduce: 4!" > "filter: 5" > "2!4!" 39 https://goo.gl/5dtLXF
  57. Array function vs RxJS const source = Rx.Observable.from([1,2,3,4,5]); source .filter(d => { console.log(`filter: ${d}`); return d % 2 === 0; }) .map(d => { console.log(`map: ${d}`); return d + '!'; }) .reduce((p, d) => { console.log(`reduce: ${d}`); return p + d; }, '') .subscribe(result => console.log(result)); > "filter: 1" > "filter: 2" > "map: 2" > "reduce: 2!" > "filter: 3" > "filter: 4" > "map: 4" > "reduce: 4!" > "filter: 5" > "2!4!" 40 https://goo.gl/5dtLXF
  58. Array function 41
  59. Array function 41
  60. RxJS 42
  61. RxJS 42
  62. fromEvent const source = Rx.Observable.fromEvent(document, ‘click'); source.subscribe(val => console.log(val)); 43
  63. fromEvent const source = Rx.Observable.fromEvent(document, 'click'); .map(event => event.x) .filter(x => x > 100); source.subscribe(val => console.log(val)); 44
  64. fromPromise const request = axios.get(URL); const source = Rx.Observable.fromPromise(request); .map(res => res.data); source.subscribe(val => console.log(val)); 45
  65. interval const source = Rx.Observable.interval(1000); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 46 https://goo.gl/ybHNDZ
  66. interval const source = Rx.Observable.interval(1000); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 46 > 0 > 1 > 2 > 3 // ... https://goo.gl/ybHNDZ
  67. interval const source = Rx.Observable.interval(1000); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 46 > 0 > 1 > 2 > 3 // ... // "completed" will not be printed https://goo.gl/ybHNDZ
  68. transformation • map • mergeMap (flatMap) 🌟 47
  69. map const source = Rx.Observable.from([1,2,3,4]) .map(d => d * 10); source.subscribe(val => console.log(val)); > 10 > 20 > 30 > 40 48
  70. mergeMap (flatMap) _.flatMap([1, 2], d => [d, d]); // [[1, 1], [2, 2]] > [1, 1, 2, 2] _.flatten([[1, 1], [2, 2]]); > [1, 1, 2, 2] 49
  71. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  72. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  73. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  74. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  75. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  76. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  77. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  78. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  79. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  80. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  81. mergeMap (flatMap) const source = requestUser$() .mergeMap(res => { const {data: {userId}} = res; return requestPosts$({userId}); }); source.subscribe(res => console.log(res)); 51 https://goo.gl/SHgVZ4
  82. mergeMap (flatMap) const source = requestUser$() .mergeMap(res => { const {data: {userId}} = res; return requestPosts$({userId}); }); source.subscribe(res => console.log(res)); 52 https://goo.gl/SHgVZ4
  83. mergeMap (flatMap) const source = requestUser$() // [.....user] .mergeMap(res => { const {data: {userId}} = res; return requestPosts$({userId}); // [....[..posts]] }); source.subscribe(res => console.log(res)); // [.....posts] 53 https://goo.gl/SHgVZ4
  84. mergeMap (flatMap) const source = requestUser$() .mergeMap(res => { const {data: {userId}} = res; return axios.get(`${URL}/posts`, {params: {id: userId}}); }); source.subscribe(res => console.log(res.data)); 54 https://goo.gl/FKaQLr
  85. filtering • filter • take • takeUntil 🌟 55
  86. filter const source = Rx.Observable.from([1,2,3,4]) .filter(d => d > 3); source.subscribe(val => console.log(val)); > 4 56
  87. take const source = Rx.Observable.interval(1000) .take(3); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 57 https://goo.gl/NkGcDo
  88. take const source = Rx.Observable.interval(1000) .take(3); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 57 > 0 > 1 > 2 > "completed" https://goo.gl/NkGcDo
  89. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  90. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  91. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  92. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  93. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  94. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 59 https://goo.gl/ZaXJFS
  95. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 60 https://goo.gl/ZaXJFS
  96. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 61 https://goo.gl/ZaXJFS
  97. takeUntil const source = Rx.Observable.interval(1000) .takeUntil(Rx.Observable.timer(3500)); source.subscribe(val => { console.log(val); }, err => { console.error(err); }, () => { console.log('completed'); }); 61 > 0 > 1 > 2 > "completed" https://goo.gl/ZaXJFS
  98. composition • combineLatest 🌟 • zip • merge 62
  99. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  100. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  101. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  102. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @
  103. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @ @ #
  104. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @ @ @ # #
  105. combineLatest const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.timer(2000).mapTo(‘@'); const source3 = Rx.Observable.timer(3000).mapTo(‘#’); Rx.Observable.combineLatest(source1, source2, source3) .subscribe(result => console.log(result)); 64 https://goo.gl/t9vpLF
  106. combineLatest const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.timer(2000).mapTo(‘@'); const source3 = Rx.Observable.timer(3000).mapTo(‘#’); Rx.Observable.combineLatest(source1, source2, source3) .subscribe(result => console.log(result)); 64 > [2, “@", “#"] > [3, “@", “#"] > [4, “@", “#"] // ... https://goo.gl/t9vpLF
  107. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  108. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  109. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  110. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  111. zip const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.interval(2000); Rx.Observable.zip(source1, source2) .subscribe(result => console.log(result)); 66 https://goo.gl/wmQW7m
  112. zip const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.interval(2000); Rx.Observable.zip(source1, source2) .subscribe(result => console.log(result)); 66 > [0, 0] > [1, 1] > [2, 2] // ... https://goo.gl/wmQW7m
  113. merge const source1 = Rx.Observable.interval(1000); const source2 = Rx.Observable.timer(2500).mapTo('source2'); Rx.Observable.merge(source1, source2) .subscribe(result => console.log(result)); > 0 > 1 > 2 > "source2" > 3 // ... 67 https://goo.gl/OqaQf9
  114. error • retry 🌟 • retryWhen 🌟 68
  115. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: ${error.message}`); }); 69 https://goo.gl/5zFqOl
  116. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: ${error.message}`); }); 70 https://goo.gl/5zFqOl
  117. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: $error.message}`); }); 71 https://goo.gl/5zFqOl
  118. retry Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retry(2) .subscribe(result => { console.log(result); }, err => { console.error(`error: $error.message}`); }); 71 https://goo.gl/5zFqOl > 0 > 1 > 2 > “error: over 2" > 0 > 1 > 2 > 0 > 1 > 2
  119. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  120. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  121. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  122. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  123. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  124. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  125. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  126. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  127. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  128. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  129. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  130. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  131. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  132. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  133. retryWhen 73 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh
  134. retryWhen 74 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh
  135. retryWhen 75 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh
  136. retryWhen 75 Rx.Observable.interval(1000) .map(val => { if (val > 2) { throw new Error('over 2'); } return val; }) .retryWhen(error => { return error .do(err => console.error(err.message)) .delay(2000); }) .subscribe(result => console.log(result)); https://goo.gl/9bhNHh > "over 2" > 0 > 1 > 2 > "over 2" > 0 > 1 > 2 > "over 2" > 0 > 1 > 2 // ...
  137. operators • switchMap 🌟 • concatMap 🌟 • publish • share • skip • bufferCount • bufferTime • Throttle • debounce 76 • concat • catch • bindNodeCallback • +others
  138. Compose async with RxJS • 여러 이벤트를 조합해야 할 때 (Drag & Drop) • 다수의 비동기 함수를 조합하는 상황 (Cache or Db, AWS Lambda) • 재시도가 필요한 경우 (on / offline) 77
  139. Drag & Drop • mousedown, mousemove, mouseup 3가지 유저 이벤트를 적절 하게 조합해야 함 - mousedown 이벤트가 발생하면 현재 엘리먼트의 초기 위치를 저장 - mousemove 이벤트가 발생하면 초기 위치 + 변한 위치를 계산해 새로운 위치로 엘리먼트를 옮김 - 언제까지? mouseup 이벤트가 발생할 때까지 78
  140. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 79 https://goo.gl/nyeK8l
  141. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 80 https://goo.gl/nyeK8l
  142. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 81 https://goo.gl/nyeK8l
  143. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 82 https://goo.gl/nyeK8l
  144. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 83 https://goo.gl/nyeK8l
  145. Drag & Drop const target = document.getElementById('target'); const down = Rx.Observable.fromEvent(target, 'mousedown'); const move = Rx.Observable.fromEvent(document, 'mousemove'); const up = Rx.Observable.fromEvent(target, 'mouseup'); const drag = down.flatMap(downEvent => { const downX = downEvent.clientX; const downY = downEvent.clientY; const downLeft = parseInt(downEvent.target.style.left, 10) || 0; const downTop = parseInt(downEvent.target.style.top, 10) || 0; return move.map(moveEvent => { moveEvent.preventDefault(); return { left: moveEvent.clientX + (downLeft - downX), top : moveEvent.clientY + (downTop - downY) }; }) .takeUntil(up); }); drag.subscribe(result => { target.style.top = result.top + 'px'; target.style.left = result.left + 'px'; }); 84 https://goo.gl/nyeK8l
  146. Cache or DB 85 • cache 또는 DB 에서 데이터를 가져오는 상황 - getFromCache$(), getFromDB$() - 둘 중에 먼저 도착한 데이터만 사용하고 싶다. - 나머지 요청은 취소되어야 함 - getFromOtherServer$() - 도착한 데이터의 값을 사용해 다른 요청 수행
  147. Cache or Db Rx.Observable.merge(getFromCache$(), getFromDB$()) .take(1) .flatMap(data => getFromOtherServer$(data.id)) .subscribe(result => { res.json(result) }, err => { res.status(500).send(err.message) }); 86
  148. Cache or Db Rx.Observable.merge(getFromCache$(), getFromDB$()) .take(1) .flatMap(data => getFromOtherServer$(data.id)) .subscribe(result => { res.json(result) }, err => { res.status(500).send(err.message) }); 87
  149. Cache or Db Rx.Observable.merge(getFromCache$(), getFromDB$()) .take(1) .flatMap(data => getFromOtherServer$(data.id)) .subscribe(result => { res.json(result) }, err => { res.status(500).send(err.message) }); 88
  150. AWS Lambda 89 • 매일 아침에 태스크 큐를 확인해서 실패한 태스크 워커가 있으면, 슬랙을 통해 알림을 받고 싶다.
  151. AWS Lambda 90 • Task function - getTasks$() - 태스크 큐에 쌓여있는 태스크 정보 가져오기 - checkNotified$() - renderTaskCount$() - 실패한 태스크가 존재하면 vega-renderer function 호출 • vega-renderer function - upload$() - 렌더링된 이미지를 s3 에 저장 - post$() - 저장된 이미지 경로와 함께 슬랙에 알림 전송
  152. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.length === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 91
  153. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.length === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 92
  154. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.length === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 93
  155. Task function export default (e, ctx, cb) => { const tasks$ = getTasks$(); const notified$ = checkNotified$(); combineLatest(tasks$, notified$, (tasks, notified) => { if (notified) { return O.return('already notified'); } else if (tasks.count === 0) { return O.return('queue empty'); } return renderTaskCount$(tasks); }) .concatAll() .subscribe(result => { ctx.callbackWaitsForEmptyEventLoop = false; cb(null, result); }, err => { ctx.callbackWaitsForEmptyEventLoop = false; cb(err); }); }; 94
  156. Task function 95 const lambda = new Aws.Lambda(); const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => { return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({ FunctionName, Payload, InvocationType }); }; export const renderTaskCount$ = values => { return invoke$('vega-renderer_png', JSON.stringify({ isLite: true, spec: Object.assign(TASK_SPEC, {data: {values}}) })) .map(res => JSON.parse(res.Payload)); };
  157. Task function 96 const lambda = new Aws.Lambda(); const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => { return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({ FunctionName, Payload, InvocationType }); }; export const renderTaskCount$ = values => { return invoke$('vega-renderer_png', JSON.stringify({ isLite: true, spec: Object.assign(TASK_SPEC, {data: {values}}) })) .map(res => JSON.parse(res.Payload)); };
  158. Task function 97 const lambda = new Aws.Lambda(); const invoke$ = (FunctionName, Payload, InvocationType = 'RequestResponse') => { return Rx.Observable.bindNodeCallback(lambda.invoke, lambda)({ FunctionName, Payload, InvocationType }); }; export const renderTaskCount$ = values => { return invoke$('vega-renderer_png', JSON.stringify({ isLite: true, spec: Object.assign(TASK_SPEC, {data: {values}}) })) .map(res => JSON.parse(res.Payload)); };
  159. vega-renderer function 98 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  160. vega-renderer function 99 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  161. vega-renderer function 100 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  162. vega-renderer function 101 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  163. vega-renderer function 102 const s3 = new Aws.S3(); const upload$ = Rx.Observable.bindNodeCallback(s3.upload, s3); export default (event, ctx, cb) => { const {spec, isLite} = event; const filename = hash(spec); spec$(spec, isLite) .map(chart => png(chart, filename)) .flatMap(image => upload$(image)) .flatMap(data => post$(event, data.Location)) .subscribe(result => { cb(null, result); }, err => { console.log(err); cb(err); }); };
  164. 103
  165. on / offline 104 • 에러가 발생했을때 - online 상태인 경우, 1초 뒤에 재시도 - offline 상태인 경우, online 상태가 되면 재시도
  166. on / offline Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val)) 105 https://goo.gl/SAmmsD
  167. on / offline 106 https://goo.gl/SAmmsD Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val))
  168. on / offline 107 https://goo.gl/SAmmsD Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val))
  169. on / offline 108 https://goo.gl/SAmmsD Rx.Observable.interval(1000) .map(val => { if (val > 5) { throw new Error('too high'); } return val; }) .retryWhen(errors => { return errors.delayWhen(() => { const online = window.navigator.onLine; return online ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online') }); }) .subscribe(val => console.log(val))
  170. Websocket on / offline const socket = Rx.Observable.webSocket('wss://echo.websocket.org') const send = document.querySelector('#send'); socket.multiplex(() => { console.log('connected') return JSON.stringify({msg: 'hi'}); }, () => { console.log('disconnected') return JSON.strinfify({msg:'all'}); }, data => { console.log(`sending: ${data.msg}`); return true; }) .retryWhen(err => { return err .do(val => console.log(`error with ${val}`)) .delayWhen(() => { return window.navigator.onLine ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online'); }); }) .subscribe(result => console.log(`received: ${result.msg}`)); send.onclick = () => socket.next(JSON.stringify({msg: 'wow'})); 109 https://goo.gl/FCS2ae
  171. Websocket on / offline const socket = Rx.Observable.webSocket('wss://echo.websocket.org') const send = document.querySelector('#send'); socket.multiplex(() => { console.log('connected') return JSON.stringify({msg: 'hi'}); }, () => { console.log('disconnected') return JSON.strinfify({msg:'all'}); }, data => { console.log(`sending: ${data.msg}`); return true; }) .retryWhen(err => { return err .do(val => console.log(`error with ${val}`)) .delayWhen(() => { return window.navigator.onLine ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online'); }); }) .subscribe(result => console.log(`received: ${result.msg}`)); send.onclick = () => socket.next(JSON.stringify({msg: 'wow'})); 110 https://goo.gl/FCS2ae
  172. Websocket on / offline const socket = Rx.Observable.webSocket('wss://echo.websocket.org') const send = document.querySelector('#send'); socket.multiplex(() => { console.log('connected') return JSON.stringify({msg: 'hi'}); }, () => { console.log('disconnected') return JSON.strinfify({msg:'all'}); }, data => { console.log(`sending: ${data.msg}`); return true; }) .retryWhen(err => { return err .do(val => console.log(`error with ${val}`)) .delayWhen(() => { return window.navigator.onLine ? Rx.Observable.timer(1000) : Rx.Observable.fromEvent(window, 'online'); }); }) .subscribe(result => console.log(`received: ${result.msg}`)); send.onclick = () => socket.next(JSON.stringify({msg: 'wow'})); 111 https://goo.gl/FCS2ae
  173. ­Dan Abramov, @JavaScript Air 025 “Rx can solve many real world tasks. And once you get used to it, you can apply it pretty much everywhere.” 112
  174. 장점 • 데이터의 변환 / 흐름에만 집중 할 수 있다. - 함수가 동기 / 비동기인지는 중요하지 않음 - Operator 는 마치 파이프 같음. - 파이프(Operator)만 잘 연결하면, 물(데이터)은 파이프를 따라 흐른다. - 이것이 Reactive Programming? - 불가능하다고 생각했던 것들을 만들 수 있게 되었다. • Observable 을 인터페이스의 중심으로 - Observable 은 거의 모든 비동기 상황을 표현할 수 있음. - 쉬워진 새로운 기능 추가 - 쉬워진 테스트 (mocking 이 수월한 경우에...) 113
  175. 단점 • 러닝커브 - 커브 정도가 아니라 절벽 수준 😱 - 코드 + 사고방식까지 바꾸어야 함 - +200 Operators, Subject, Scheduler... - 처음에는 무섭지만, 막상 사용하다보면 많이 사용하게 되는 건 7 ~ 8 개 정도? • 디버깅 - 길고, 복잡한 call stack • RxJS 의 내부 타입 / 구현을 모른다면 거의 쓸모없는 정보들 😥 • operator 를 제대로 이해하고 사용한다면 거의 볼 일이 없음 • RxJS@5 에서는 그나마 조금 줄었음 - do operator 를 활용하세요!! • 서버 보다는 UI 개발에 더 많은 도움을 줄 수 있다고 생각함 🙏 114
  176. Who to follow • Erik Meijer - creator of reactive-extensions - 📹 One hacker way • Matthew Podwyski - initial creator of RxJS • Ben Lesh - RxJS@5 main contributor 115 • André Staltz - RxJS@5 main contributor - creator of cycle.js • kris kowal - creator of Q - 📰 general theory of reactivity
  177. What to see • https://github.com/reactivex/rxjs • 📰 official RxJS@5 doc • 📹 💵 egghead RxJS series - RxJS Beyond the Basics: Creating Observables from scratch - RxJS Beyond the Basics: Operators in Depth • 📖 learn-rxjs 116
  178. Q & A 117
Advertisement