Compose Async with RxJS

1,409 views

Published on

play.node 2016 session slides. http://playnode.io/2016/#schedule

Published in: Software
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,409
On SlideShare
0
From Embeds
0
Number of Embeds
785
Actions
Shares
0
Downloads
36
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide

Compose Async with RxJS

  1. 1. Compose Async with RxJS @chitacan, Riiid 1
  2. 2. https://goo.gl/W1YChu 2 JSBin for example
  3. 3. RxJS ? Reactive extensions library for JavaScript 3
  4. 4. 시작하기 전에 [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 4
  5. 5. 시작하기 전에 [1, 2, 3, 4] .map(d => d * 2) .filter(d => d > 4) .forEach(console.log); 4 > 6 > 8
  6. 6. Event 를 이렇게 처리한다면 어떨까요? 5
  7. 7. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 6
  8. 8. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 7
  9. 9. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 8
  10. 10. 시작하기 전에 [..Event{}..Event{}...Event{}...] .map(e => e.clientX * 2) .filter(x => x > 4) .forEach(updateUI); 9
  11. 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. 12. Array 와 Event 는 모두 Collections 11
  13. 13. Event 는 데이터의 모음이고, Array 처럼 처리할 수 있다. 12
  14. 14. Array 와 Event • 차이점 - array 는 각 요소를 동기적 (sync) 으로 조회할 수 있고, 끝이 있다. - event 는 각 요소를 비동기적 (async) 으로 조회할 수 있고, 끝이 없다.
 (하지만 event listening 을 취소 할 수 있다.) • 비동기적으로 생성되는 요소들을 표현하고, 원하는 시점에 취소할 수 있는 타입 13
  15. 15. Observable 14
  16. 16. [ ] time collections over time 15
  17. 17. [ ] time collections over time 15 1....2..3..
  18. 18. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16
  19. 19. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3
  20. 20. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1
  21. 21. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2
  22. 22. pull vs push [1, 2, 3].forEach(console.log); [1..2..3...].forEach(console.log); 16 > 1 > 2 > 3 > 1 > 2 > 3 // ...
  23. 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. 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. 25. “RxJS 는 Observable 타입을 활용해 Event를 Array 처럼 처리 할 수 있는 라이브러리” 17
  26. 26. ­Ben Lesh, RxJS In-Depth @AngularConnect 2015 “RxJS is LoDash or Underscore for async” 18
  27. 27. RxJS • Observable 타입 • Operators (map, filter ...) • Scheduler 19
  28. 28. Observable vs Promise Promise Observable single value multiple value not lazy lazy not cancelable cancelable no completion callback completion callback 20
  29. 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. 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. 31. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 23
  32. 32. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 24 executor
  33. 33. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 25 https://goo.gl/tK39aS
  34. 34. Promise: not lazy const p = new Promise(resolve => { setTimeout(() => { resolve(‘run'); }, 1000); console.log('started'); }); 25 > "started" https://goo.gl/tK39aS
  35. 35. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); Observable: lazy 26
  36. 36. const source = Rx.Observable.create(observer => { setTimeout(() => { observer.next('run'); }, 1000); console.log('started'); }); Observable: lazy 26 // no console output
  37. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 48. promise.then(valueFn, errorFn); Completion 31
  49. 49. Completion 32 observable.subscribe(nextFn, errorFn, completeFn);
  50. 50. Operators • observable 타입을 변환 / 조합할 수 있음 • RxJS@5 기준 +200개 • 종류 - creation - transformation - filtering - composition - error - ... 33
  51. 51. creation • create • from • fromEvent • fromPromise • interval 34
  52. 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. 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. 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. 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. 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. 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. 58. Array function 41
  59. 59. Array function 41
  60. 60. RxJS 42
  61. 61. RxJS 42
  62. 62. fromEvent const source = Rx.Observable.fromEvent(document, ‘click'); source.subscribe(val => console.log(val)); 43
  63. 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. 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. 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. 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. 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. 68. transformation • map • mergeMap (flatMap) 🌟 47
  69. 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. 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. 71. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  72. 72. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  73. 73. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  74. 74. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  75. 75. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  76. 76. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  77. 77. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  78. 78. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  79. 79. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  80. 80. mergeMap (flatMap) [..1..2..3].mergeMap(d => [...d]) [[...1]..[...2]..[...3]] [....1......2......3] 50 source projection result
  81. 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. 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. 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. 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. 85. filtering • filter • take • takeUntil 🌟 55
  86. 86. filter const source = Rx.Observable.from([1,2,3,4]) .filter(d => d > 3); source.subscribe(val => console.log(val)); > 4 56
  87. 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. 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. 89. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  90. 90. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  91. 91. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  92. 92. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  93. 93. takeUntil [...0...1...2...3].takeUntil( [.............0]) [...0...1...2.] 58 source notifier result
  94. 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. 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. 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. 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. 98. composition • combineLatest 🌟 • zip • merge 62
  99. 99. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  100. 100. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  101. 101. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63
  102. 102. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @
  103. 103. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @ @ #
  104. 104. combineLatest combineLatest( [...0...1...2...3...4...], // source1 [.......@], // source2 [...........#] // source3 ) [ ...........[2, @, #]...[3, @, #]...[4, @, #]... ] 63 @ @ @ # #
  105. 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. 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. 107. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  108. 108. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  109. 109. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  110. 110. zip zip( [...0...1...2...3...4...5], // source1 [.....0.....1.....2...] // source2 ) [ ....[0, 0]....[1, 1]....[2, 2]... ] 65
  111. 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. 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. 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. 114. error • retry 🌟 • retryWhen 🌟 68
  115. 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. 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. 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. 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. 119. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  120. 120. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  121. 121. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  122. 122. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  123. 123. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  124. 124. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  125. 125. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  126. 126. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  127. 127. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  128. 128. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  129. 129. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result 💥
  130. 130. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  131. 131. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  132. 132. retryWhen [...0...1...2...3].retryWhen(err => [......0]) [...0...1...2........0...1...2........0...1...2.] 72 source notifier result
  133. 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. 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. 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. 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. 137. operators • switchMap 🌟 • concatMap 🌟 • publish • share • skip • bufferCount • bufferTime • Throttle • debounce 76 • concat • catch • bindNodeCallback • +others
  138. 138. Compose async with RxJS • 여러 이벤트를 조합해야 할 때 (Drag & Drop) • 다수의 비동기 함수를 조합하는 상황 (Cache or Db, AWS Lambda) • 재시도가 필요한 경우 (on / offline) 77
  139. 139. Drag & Drop • mousedown, mousemove, mouseup 3가지 유저 이벤트를 적절 하게 조합해야 함 - mousedown 이벤트가 발생하면 현재 엘리먼트의 초기 위치를 저장 - mousemove 이벤트가 발생하면 초기 위치 + 변한 위치를 계산해 새로운 위치로 엘리먼트를 옮김 - 언제까지? mouseup 이벤트가 발생할 때까지 78
  140. 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. 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. 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. 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. 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. 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. 146. Cache or DB 85 • cache 또는 DB 에서 데이터를 가져오는 상황 - getFromCache$(), getFromDB$() - 둘 중에 먼저 도착한 데이터만 사용하고 싶다. - 나머지 요청은 취소되어야 함 - getFromOtherServer$() - 도착한 데이터의 값을 사용해 다른 요청 수행
  147. 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. 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. 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. 150. AWS Lambda 89 • 매일 아침에 태스크 큐를 확인해서 실패한 태스크 워커가 있으면, 슬랙을 통해 알림을 받고 싶다.
  151. 151. AWS Lambda 90 • Task function - getTasks$() - 태스크 큐에 쌓여있는 태스크 정보 가져오기 - checkNotified$() - renderTaskCount$() - 실패한 태스크가 존재하면 vega-renderer function 호출 • vega-renderer function - upload$() - 렌더링된 이미지를 s3 에 저장 - post$() - 저장된 이미지 경로와 함께 슬랙에 알림 전송
  152. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 164. 103
  165. 165. on / offline 104 • 에러가 발생했을때 - online 상태인 경우, 1초 뒤에 재시도 - offline 상태인 경우, online 상태가 되면 재시도
  166. 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. 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. 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. 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. 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. 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. 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. 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. 174. 장점 • 데이터의 변환 / 흐름에만 집중 할 수 있다. - 함수가 동기 / 비동기인지는 중요하지 않음 - Operator 는 마치 파이프 같음. - 파이프(Operator)만 잘 연결하면, 물(데이터)은 파이프를 따라 흐른다. - 이것이 Reactive Programming? - 불가능하다고 생각했던 것들을 만들 수 있게 되었다. • Observable 을 인터페이스의 중심으로 - Observable 은 거의 모든 비동기 상황을 표현할 수 있음. - 쉬워진 새로운 기능 추가 - 쉬워진 테스트 (mocking 이 수월한 경우에...) 113
  175. 175. 단점 • 러닝커브 - 커브 정도가 아니라 절벽 수준 😱 - 코드 + 사고방식까지 바꾸어야 함 - +200 Operators, Subject, Scheduler... - 처음에는 무섭지만, 막상 사용하다보면 많이 사용하게 되는 건 7 ~ 8 개 정도? • 디버깅 - 길고, 복잡한 call stack • RxJS 의 내부 타입 / 구현을 모른다면 거의 쓸모없는 정보들 😥 • operator 를 제대로 이해하고 사용한다면 거의 볼 일이 없음 • RxJS@5 에서는 그나마 조금 줄었음 - do operator 를 활용하세요!! • 서버 보다는 UI 개발에 더 많은 도움을 줄 수 있다고 생각함 🙏 114
  176. 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. 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. 178. Q & A 117

×