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. 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; }); } });
  2. 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; }); } }); 篷褖笔㵕
  3. 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; 戢㲘笔㵕Ԫկ
  4. 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; }); ڣ䥁笔㵕ṛଶ
  5. 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
  6. 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 !=> {
  7. 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
  8. 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
  9. 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
  10. 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; }); } }); 篷褖笔㵕
  11. 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; }); } }); 獋㮆覍ݶྍᤈ傶
  12. 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
  13. 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; }); } });
  14. 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);
  15. 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); ౯㮉Ӟਧᥝ䌃
 蝡讕蠨ጱ纷ୗ嘨㻟牫
  16. 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
  17. 28.

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

    = mouseMove .subscribe(x !=> console.log(x)); subscription.unsubscribe();
  18. 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: () !=> {}, });
  19. 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: () !=> {}, });
  20. 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: () !=> {}, });
  21. 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: () !=> {}, });
  22. 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: () !=> {}, });
  23. 35.
  24. 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|)
  25. 41.

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

    % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  26. 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 });
  27. 64.
  28. 67.
  29. 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
  30. 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 妔虋碍
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 75.

    import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable

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

    import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable

    .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); ು櫝 observable creator
  38. 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 });
  39. 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 }); 硯ک加缏ጱ䲆礯
  40. 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 > );
  41. 83.

    const scrollOver = criticalP !=> pipe( map(event !=> event.target), map(hasScroll),

    filter(p !=> p > criticalP) ); 硬አ lettable operator import { map, filter } from 'rxjs/operators';
  42. 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 });
  43. 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 });
  44. 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 });
  45. 89.

    ইຎ᯿手ӣ稞Ֆ०硻牧 ࢧ㯽毆戔独 const defaultData = { success: false, data: []

    }; const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3) .catch(() !=> Observable.of(defaultData));
  46. 90.

    ࢧ㯽 Array const defaultData = { success: false, data: []

    }; const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3) .catch(() !=> [defaultData]);
  47. 91.

    硬አ lettable operator import { retry, catchError } from 'rxjs/operators';

    const defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .pipe( retry(3), catchError(() !=> [defaultData]) );
  48. 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) );
  49. 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 '%%...';
  50. 102.
  51. 109.

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

    }); mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$)) .subscribe(value !=> { #// do something }); 玲窞藶穩 瞫೉
  52. 110.
  53. 113.
  54. 114.
  55. 115.
  56. 116.

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

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

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

    100% ݢ᯿蕦ጱ介手 • RxJS ൉׀碻樌秇硈 • 犋襑ᥝ臺碻樌缛盃介手奾ຎ
  58. 119.

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

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