Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RxJS - 封裝程式的藝術

9b753d898d93aae8bd163db5c420a1ae?s=47 Jerry Hong
November 04, 2017

RxJS - 封裝程式的藝術

這幾年來 JavaScript 有越來越多的語法糖(syntax sugar)像是 async/await, generator 等等,但我們實際上在處理非同步行為時,仍然要透過各種不同的方式;這使我們必須學習越來越多的語法,但程式碼卻更加難以閱讀。本次演講將會說明 RxJS 如何使用相同的方式處理各種非同步行為,以及我們要如何運用 Functional Programming 的觀念把複雜的非同步行為封裝成簡單可讀的程式碼。

9b753d898d93aae8bd163db5c420a1ae?s=128

Jerry Hong

November 04, 2017
Tweet

Transcript

  1. RxJS ੗蕕纷ୗጱ萬蔩

  2. What’s RxJS ?

  3. Lodash for async

  4. Ӟ㮆蝚螂 Observable 奲ݳݱ圵覍ݶྍᤈ傶ጱ Library

  5. Why we need RxJS ?

  6. let isRequesting = false; scrollView.addEventListener('scroll', event !=> { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } });
  7. let isRequesting = false; scrollView.addEventListener('scroll', event !=> { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } }); 篷褖笔㵕
  8. let isRequesting = false; scrollView.addEventListener('scroll', event ! const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; 戢㲘笔㵕Ԫկ
  9. let isRequesting = false; scrollView.addEventListener('scroll', event ! const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); ڣ䥁笔㵕ṛଶ
  10. scrollView.addEventListener('scroll', event ! const DOM = event.target; if(hasScrolled(DOM) > 0.9

    #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } }); 咳蝑 Request
  11. 戔ਧ Flag let isRequesting = false; scrollView.addEventListener('scroll', event ! const

    DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> {
  12. let isRequesting = false; scrollView.addEventListener('scroll', event ! const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); 舙 isRequesting 傶 false 
 ٚ咳蝑 Request
  13. let isRequesting = false; scrollView.addEventListener('scroll', event ! const DOM =

    event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } 咳蝑 Request 獮牧 戔ਧ isRequest 傶 true
  14. const DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting

    = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } }); Response 盅牧 戔ਧ isRequest 傶 false
  15. let isRequesting = false; scrollView.addEventListener('scroll', event !=> { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } }); 篷褖笔㵕
  16. What’s wrong ?

  17. let isRequesting = false; scrollView.addEventListener('scroll', event !=> { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } }); 獋㮆覍ݶྍᤈ傶
  18. let isRequesting = false; scrollView.addEventListener('scroll', event !=> { const DOM

    = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } }); ݶ䰬ฎ覍ݶྍᤈ傶
 㶴አ犋ݶጱ pattern
  19. Flag let isRequesting = false; scrollView.addEventListener('scroll', event !=> { const

    DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; }); } });
  20. More Flags let isRequesting = false; let requestCount = 0;

    const scrollHandler = function(event) { const DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; requestCount = requestCount + 1; if (requestCount %%=== 3) { scrollView.removeEventListener('scroll', scrollHandler) } }) } } scrollView.addEventListener('scroll', scrollHandler);
  21. let isRequesting = false; let requestCount = 0; const scrollHandler

    = function(event) { const DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; requestCount = requestCount + 1; if (requestCount %%=== 3) { scrollView.removeEventListener('scroll', scrollHandler) } }) } } scrollView.addEventListener('scroll', scrollHandler); ౯㮉Ӟਧᥝ䌃
 蝡讕蠨ጱ纷ୗ嘨㻟牫
  22. let isRequesting = false; let requestCount = 0; const scrollHandler

    = function(event) { const DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) { isRequesting = true; fetch('url%%...') .then(res !=> { #// do something change view isRequesting = false; requestCount = requestCount + 1; if (requestCount %%=== 3) { scrollView.removeEventListener('scroll', scrollHandler) } }) } } scrollView.addEventListener('scroll', scrollHandler); Observable.fromEvent(scrollView, 'scroll') .map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > 0.9) .exhaustMap(() !=> fetch('url%%...')) .take(3) .subscribe(res !=> { #// do something change view }); VanillaJS RxJS
  23. Jerry Hong Front-End Engineer | Website: blog.jerry-hong.com Facebook: J.H.MingChen

  24. Observable 墋Օ Observable 墋Օ

  25. What is Observable ?

  26. What is Observable ? Collection + Time

  27. Observable 疰猟ฎӞ㮆ଧڜ牧
 愊ᶎጱزᔰ䨝褰茐碻樌വ蝑 What is Observable ?

  28. var mouseMove = Observable .fromEvent(DOM, 'mousemove'); observable ୌ缏膏懪褂 var subscription

    = mouseMove .subscribe(x !=> console.log(x)); subscription.unsubscribe();
  29. Observable.of(2, 3, 4); Observable.from([2, 3, 4]); Observable.from(fetch('url')); Observable.ajax('url'); Observable.fromEvent(DOM, 'click');

    Observable.interval(1000); ୌ缏 observable
  30. var sub = Observable .from([1, 2, 3]) .map(x !=> x

    + 1) .filter(x !=> x % 2 %%=== 0) .subscribe({ next: x !=> console.log(x), error: err !=> {}, complete: () !=> {}, });
  31. observable • Observable ጱᇔկ䋿ֺ • ࣁ๚ᤩ懪褂ԏ獮牧ݝฎ 㮆ᇔկ牧犋䨝蝑ڊزᔰ • ݢᤩ懪褂(subscribe) •

    ٍ磪ग़圵 operators var sub = Observable .from([1, 2, 3]) .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 0) .subscribe({ next: x !=> console.log(x), error: err !=> {}, complete: () !=> {}, });
  32. operator • Observable ጱොဩ • ݢ䌘زᔰ֢螀ᓒ蒂ቘ • ࿞螐ࢧ㯽Ӟ㮆碝ጱ observable (磪ֺक़)

    var sub = Observable .from([1, 2, 3]) .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 0) .subscribe({ next: x !=> console.log(x), error: err !=> {}, complete: () !=> {}, });
  33. observer • አ㬵懪褂 observable ጱ ᇔկ • next 獢ୗӞਧᥝ磪牧
 error

    膏 complete ݢ螡 䢔 .from([1, 2, 3]) .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 0) .subscribe({ next: x !=> console.log(x), error: err !=> {}, complete: () !=> {}, });
  34. subscription • observable 懪褂盅ࢧ㯽 ጱᇔկ • ݢ೭㬵蝐懪 (unsubscribe) • ݢ犥ٌ犢懪㻌ݳ㬫

    var sub = Observable .from([1, 2, 3]) .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 0) .subscribe({ next: x !=> console.log(x), error: err !=> {}, complete: () !=> {}, });
  35. None
  36. Marble Diagram --a--b--c--d--e|

  37. Marble Diagram - Ӟੜྦྷ碻樌 (10 frames) n(0-9/a-z) 蝑ڊጱزᔰ(next) | 蝑ڊ奾๳

    (complete) # 蝑ڊ梊藮 (error) () ݶྍ蝑ڊ time ----0---1---2---3-- ----0---1---2---3| ----0---1---2---3--# (123|)
  38. Observable.of(1, 2, 3); (123|)

  39. Observable.interval(10) - -012 -01 -0 -0123 -01234 -01234..

  40. Observable .fromEvent(DOM, 'click') ---e--ee-e--e-...

  41. Observable.interval(10) .take(3) .map(x !=> x + 1) .filter(x !=> x

    % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  42. Observable.interval(20) --0-1-2-3-4-5-6-7.. -------e----------- --0-1-2| .takeUntil( Observable .fromEvent(DOM, 'click') );

  43. Observable .fromEvent(DOM, 'click') .map(() !=> ajax('url%%...')) .mergeAll(); -----e-e---- -----o-o---- \

    \ \ ----r| ----r| ---------r-r-
  44. Observable .fromEvent(DOM, 'click') .mergeMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r| ---------r-r-
  45. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r| -----------r-
  46. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  47. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  48. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  49. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  50. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  51. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  52. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  53. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  54. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r|
  55. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| --!
  56. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| --!
  57. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| --!
  58. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| --!
  59. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| --!
  60. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| --! -----------r-
  61. Observable .fromEvent(DOM, 'click') .exhaustMap(() !=> fetch('url%%...')) -----e-e---- -----o-o---- \ \

    \ ----r| ----r| ---------r---
  62. Observable.fromEvent(scrollView, 'scroll') .map(event !=> event.target) .map(hasScrolled) .filter(p !=> p >

    0.9) .exhaustMap(() !=> fetch('url%%...')) .take(3) .subscribe(res !=> { #// do something change view });
  63. Make Your Code Clean

  64. Readable

  65. Readable Composable

  66. Readable Testable Composable

  67. Readable

  68. Observable.fromEvent(scrollView, 'scroll') .map(event !=> event.target) .map(hasScrolled) .filter(p !=> p >

    0.9) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); ು玲磪఺嬝ጱ
 Observable
  69. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .map(event !=> event.target)

    .map(hasScrolled) .filter(p !=> p > 0.9) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); Assign 妔虋碍
  70. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .map(event !=> event.target)

    .map(hasScrolled) .filter(p !=> p > 0.9) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); ು玲ڊ
 蝢አጱ Operator
  71. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); const scrollOverNinetyPercent = Obs

    !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > 0.9); scrollOverNinetyPercent(scroll$) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); Naming Function
  72. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); const scrollOverNinetyPercent = Obs

    !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > 0.9); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); 硬አ let operator
  73. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); const scrollOverNinetyPercent = Obs

    !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > 0.9); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); 硬አ let operator
  74. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scrollOverNinetyPercent scroll$ .let(scrollOverNinetyPercent) .exhaustMap(()

    !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); const scroll$ = Observable .fromEvent(scrollView, 'scroll'); const scrollOverNinetyPercent = Obs !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > 0.9); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); 硬አ let operator
  75. import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable

    .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); 硯ک加缏ጱ䲆礯
  76. import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable

    .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); ು櫝 observable creator
  77. import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable

    .fromEvent(scrollView, 'scroll'); const getPostObservable = () !=> fetch('url%%...');
 scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view }); 狕硬ܻ๜ጱ纷ୗ嘨 import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); getPostObservable
 scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  78. import { scrollOverNinetyPercent } from '%%...'; import { getPostObservable }

    from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view }); 硯ک加缏ጱ䲆礯
  79. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res

    !=> { #// do something change view });
  80. Composable

  81. const scrollOverNinetyPercent = Obs !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p

    !=> p > 0.9); 犋অ᯿奲
  82. const scrollOver = criticalP !=> Obs !=> Obs.map(event !=> event.target)

    .map(hasScrolled) .filter(p !=> p > criticalP); Higher Order Function const scrollOver = Obs !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > );
  83. const scrollOver = criticalP !=> pipe( map(event !=> event.target), map(hasScroll),

    filter(p !=> p > criticalP) ); 硬አ lettable operator import { map, filter } from 'rxjs/operators';
  84. ܻ๜ጱ纷ୗ嘨 import { scrollOverNinetyPercent } from '%%...'; import { getPostObservable

    } from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  85. 狕硬盅 import { scrollOver } from '%%...'; import { getPostObservable

    } from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOver(0.9)) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  86. 硬አ pipe 奲ݳ operator import { scrollOver } from '%%...';

    import { getPostObservable } from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .pipe( scrollOver(0.9), exhaustMap(getPostObservable) ) .subscribe(res !=> { #// do something change view }); import { scrollOver } from '%%...'; import { getPostObservable } from 'xxx'; import { exhaustMap } from 'rxjs/operators'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .pipe( scrollOver(0.9), exhaustMap(getPostObservable) ) .subscribe(res !=> { #// do something change view });
  87. 玲஑෈ᒍ const getPostObservable = () !=> Observable.ajax('url%%...');

  88. ०硻盅 retry 3 稞 const getPostObservable = () !=> Observable.ajax('url%%...')

    .retry(3);
  89. ইຎ᯿手ӣ稞Ֆ०硻牧 ࢧ㯽毆戔独 const defaultData = { success: false, data: []

    }; const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3) .catch(() !=> Observable.of(defaultData));
  90. ࢧ㯽 Array const defaultData = { success: false, data: []

    }; const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3) .catch(() !=> [defaultData]);
  91. 硬አ lettable operator import { retry, catchError } from 'rxjs/operators';

    const defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .pipe( retry(3), catchError(() !=> [defaultData]) );
  92. ୌ缏ᛔ૩ጱ lettable operator import { retry, catchError } from 'rxjs/operators';

    const defaultData = { success: false, data: [] }; const onErrorReturn = defaultData !=> catchError(() !=> [defaultData]); const getPostObservable = () !=> Observable.ajax('url%%...') .pipe( retry(3), onErrorReturn(defaultData) );
  93. 硯ک加缏ጱ䲆礯 import { retry, catchError } from 'rxjs/operators'; onErrorReturn const

    defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .pipe( retry(3), onErrorReturn(defaultData) ); import { onErrorReturn } from '%%...';
  94. 哴ݢ胼ֵአ Observable

  95. Observable is composable

  96. ᇔկ瞫೉ (Drag&Drop) https://dribbble.com/shots/1074817-Drag-Drop-List-GIF

  97. mouseDown$

  98. mouseDown$ .switchMap(() !=> mouseMove$) mouseDown$

  99. mouseDown$ .switchMap(() !=> mouseMove$)

  100. mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$))

  101. mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$)) .subscribe(value !=> { #// do something

    });
  102. None
  103. mouseClick$

  104. mouseClick$ .switchMap(() !=> request$) mouseClick$

  105. mouseClick$ .switchMap(() !=> request$)

  106. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$))

  107. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something

    });
  108. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something

    });
  109. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something

    }); mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$)) .subscribe(value !=> { #// do something }); 玲窞藶穩 瞫೉
  110. Testable

  111. Testing Asynchronous Code is Hard

  112. Testing Asynchronous Code is Hard • 秇硈覍ݶྍ介手 • 眐ह蕦褾犋অ秇硈牧Ӭ਻ฃڊ梊 •

    ڊ梊碻犋Ꭳ螇ᥝತ抑 • 羊嘦覍ݶྍ介手
  113. None
  114. None
  115. None
  116. Testing Asynchronous Code is Hard • 秇硈覍ݶྍ介手 • 眐ह蕦褾犋অ秇硈牧Ӭ਻ฃڊ梊 •

    ڊ梊碻犋Ꭳ螇ᥝತ抑 • 羊嘦覍ݶྍ介手 • 介手䨝臺揲螂ग़ጱ碻樌
  117. Marble Testing

  118. Marble Testing • አ Marble Diagram 砰䌃介手 • ݝᥝ䨝向瑽疰胼䌃介手 •

    100% ݢ᯿蕦ጱ介手 • RxJS ൉׀碻樌秇硈 • 犋襑ᥝ臺碻樌缛盃介手奾ຎ
  119. Observable.interval(10) .take(3) .map(x !=> x + 1) .filter(x !=> x

    % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  120. it('interval', () !=> { const actual = Observable.interval(10, testScheduler) .take(3)

    .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 1); testScheduler.expectObservable(actual) .toBe('-a-(b|)', { a: 1, b: 3 }); });
  121. https://youtu.be/i2A1S9o7ZFQ

  122. Programming is thinking, not typing – Cassey Pottan

  123. Be a Programmer, not just a Coder