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

RxJS - 封裝程式的藝術

Jerry Hong
November 04, 2017

RxJS - 封裝程式的藝術

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

Jerry Hong

November 04, 2017
Tweet

More Decks by Jerry Hong

Other Decks in Technology

Transcript

  1. 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; }); } });
  2. 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; }); } }); 篷褖笔㵕
  3. 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; 戢㲘笔㵕Ԫկ
  4. 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; }); ڣ䥁笔㵕ṛଶ
  5. 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
  6. 戔ਧ Flag let isRequesting = false; scrollView.addEventListener('scroll', event ! const

    DOM = event.target; if(hasScrolled(DOM) > 0.9 #&& !isRequesting) isRequesting = true; fetch('url%%...') .then(res !=> {
  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; }); 舙 isRequesting 傶 false 
 ٚ咳蝑 Request
  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; }); } 咳蝑 Request 獮牧 戔ਧ isRequest 傶 true
  9. 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
  10. 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; }); } }); 篷褖笔㵕
  11. 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; }); } }); 獋㮆覍ݶྍᤈ傶
  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; }); } }); ݶ䰬ฎ覍ݶྍᤈ傶
 㶴አ犋ݶጱ pattern
  13. 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; }); } });
  14. 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);
  15. 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); ౯㮉Ӟਧᥝ䌃
 蝡讕蠨ጱ纷ୗ嘨㻟牫
  16. 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
  17. var mouseMove = Observable .fromEvent(DOM, 'mousemove'); observable ୌ缏膏懪褂 var subscription

    = mouseMove .subscribe(x !=> console.log(x)); subscription.unsubscribe();
  18. 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: () !=> {}, });
  19. 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: () !=> {}, });
  20. 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: () !=> {}, });
  21. 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: () !=> {}, });
  22. 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: () !=> {}, });
  23. 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|)
  24. Observable.interval(10) .take(3) .map(x !=> x + 1) .filter(x !=> x

    % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  25. 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 });
  26. 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
  27. 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 妔虋碍
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable

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

    .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); ು櫝 observable creator
  35. 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 });
  36. import { scrollOverNinetyPercent } from '%%...'; import { getPostObservable }

    from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view }); 硯ک加缏ጱ䲆礯
  37. 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 > );
  38. const scrollOver = criticalP !=> pipe( map(event !=> event.target), map(hasScroll),

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

    } from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  40. 狕硬盅 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 });
  41. 硬አ 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 });
  42. ইຎ᯿手ӣ稞Ֆ०硻牧 ࢧ㯽毆戔独 const defaultData = { success: false, data: []

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

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

    const defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .pipe( retry(3), catchError(() !=> [defaultData]) );
  45. ୌ缏ᛔ૩ጱ 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) );
  46. 硯ک加缏ጱ䲆礯 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 '%%...';
  47. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something

    }); mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$)) .subscribe(value !=> { #// do something }); 玲窞藶穩 瞫೉
  48. Testing Asynchronous Code is Hard • 秇硈覍ݶྍ介手 • 眐ह蕦褾犋অ秇硈牧Ӭ਻ฃڊ梊 •

    ڊ梊碻犋Ꭳ螇ᥝತ抑 • 羊嘦覍ݶྍ介手 • 介手䨝臺揲螂ग़ጱ碻樌
  49. Marble Testing • አ Marble Diagram 砰䌃介手 • ݝᥝ䨝向瑽疰胼䌃介手 •

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

    % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  51. 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 }); });