Se ha denunciado esta presentación.
Se está descargando tu SlideShare. ×

RxJS - 封裝程式的藝術

Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Anuncio
Cargando en…3
×

Eche un vistazo a continuación

1 de 123 Anuncio

RxJS - 封裝程式的藝術

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

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

Anuncio
Anuncio

Más Contenido Relacionado

Presentaciones para usted (20)

Similares a RxJS - 封裝程式的藝術 (20)

Anuncio

Más reciente (20)

RxJS - 封裝程式的藝術

  1. 1. RxJS
  2. 2. What’s RxJS ?
  3. 3. Lodash for async
  4. 4. Observable Library
  5. 5. Why we need RxJS ?
  6. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 16. What’s wrong ?
  17. 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. 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. 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. 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. 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. 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. 23. Jerry Hong Front-End Engineer | Website: blog.jerry-hong.com Facebook: J.H.MingChen
  24. 24. ObservableObservable
  25. 25. What is Observable ?
  26. 26. What is Observable ? Collection+ Time
  27. 27. Observable 
 What is Observable ?
  28. 28. var mouseMove = Observable .fromEvent(DOM, 'mousemove'); observable var subscription = mouseMove .subscribe(x !=> console.log(x)); subscription.unsubscribe();
  29. 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. 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. 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. 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. 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. 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. 35. Marble Diagram --a--b--c--d--e|
  36. 36. 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|)
  37. 37. Observable.of(1, 2, 3); (123|)
  38. 38. Observable.interval(10) --012-01-0-0123-01234-01234..
  39. 39. Observable .fromEvent(DOM, 'click') ---e--ee-e--e-...
  40. 40. Observable.interval(10) .take(3) .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  41. 41. Observable.interval(20) --0-1-2-3-4-5-6-7.. -------e----------- --0-1-2| .takeUntil( Observable .fromEvent(DOM, 'click') );
  42. 42. Observable .fromEvent(DOM, 'click') .map(() !=> ajax('url%%...')) .mergeAll(); -----e-e---- -----o-o---- ----r| ----r| ---------r-r-
  43. 43. Observable .fromEvent(DOM, 'click') .mergeMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r| ---------r-r-
  44. 44. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r| -----------r-
  45. 45. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  46. 46. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  47. 47. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  48. 48. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  49. 49. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  50. 50. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  51. 51. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  52. 52. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  53. 53. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| ----r|
  54. 54. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| --!
  55. 55. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| --!
  56. 56. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| --!
  57. 57. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| --!
  58. 58. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| --!
  59. 59. Observable .fromEvent(DOM, 'click') .switchMap(() !=> ajax('url%%...')) -----e-e---- -----o-o---- ----r| --! -----------r-
  60. 60. Observable .fromEvent(DOM, 'click') .exhaustMap(() !=> fetch('url%%...')) -----e-e---- -----o-o---- ----r| ----r| ---------r---
  61. 61. 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 });
  62. 62. Make Your Code Clean
  63. 63. Readable
  64. 64. Readable Composable
  65. 65. Readable TestableComposable
  66. 66. Readable
  67. 67. 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
  68. 68. 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
  69. 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 }); 
 Operator
  70. 70. 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
  71. 71. 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
  72. 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. 73. 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
  74. 74. import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view });
  75. 75. import { scrollOverNinetyPercent } from '%%...'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(() !=> fetch('url%%...')) .subscribe(res !=> { #// do something change view }); observable creator
  76. 76. 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 });
  77. 77. import { scrollOverNinetyPercent } from '%%...'; import { getPostObservable } from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  78. 78. const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  79. 79. Composable
  80. 80. const scrollOverNinetyPercent = Obs !=> Obs.map(event !=> event.target) .map(hasScrolled) .filter(p !=> p > 0.9);
  81. 81. 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 > );
  82. 82. const scrollOver = criticalP !=> pipe( map(event !=> event.target), map(hasScroll), filter(p !=> p > criticalP) ); lettable operator import { map, filter } from 'rxjs/operators';
  83. 83. import { scrollOverNinetyPercent } from '%%...'; import { getPostObservable } from 'xxx'; const scroll$ = Observable .fromEvent(scrollView, 'scroll'); scroll$ .let(scrollOverNinetyPercent) .exhaustMap(getPostObservable) .subscribe(res !=> { #// do something change view });
  84. 84. 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 });
  85. 85. 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 });
  86. 86. const getPostObservable = () !=> Observable.ajax('url%%...');
  87. 87. retry 3 const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3);
  88. 88. const defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3) .catch(() !=> Observable.of(defaultData));
  89. 89. Array const defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .retry(3) .catch(() !=> [defaultData]);
  90. 90. lettable operator import { retry, catchError } from 'rxjs/operators'; const defaultData = { success: false, data: [] }; const getPostObservable = () !=> Observable.ajax('url%%...') .pipe( retry(3), catchError(() !=> [defaultData]) );
  91. 91. 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) );
  92. 92. 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 '%%...';
  93. 93. Observable
  94. 94. Observable is composable
  95. 95. (Drag&Drop) https://dribbble.com/shots/1074817-Drag-Drop-List-GIF
  96. 96. mouseDown$
  97. 97. mouseDown$ .switchMap(() !=> mouseMove$) mouseDown$
  98. 98. mouseDown$ .switchMap(() !=> mouseMove$)
  99. 99. mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$))
  100. 100. mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$)) .subscribe(value !=> { #// do something });
  101. 101. mouseClick$
  102. 102. mouseClick$ .switchMap(() !=> request$) mouseClick$
  103. 103. mouseClick$ .switchMap(() !=> request$)
  104. 104. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$))
  105. 105. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something });
  106. 106. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something });
  107. 107. mouseClick$ .switchMap(() !=> request$.takeUntil(cancel$)) .subscribe(value !=> { #// do something }); mouseDown$ .switchMap(() !=> mouseMove$.takeUntil(mouseUp$)) .subscribe(value !=> { #// do something });
  108. 108. Testable
  109. 109. Testing Asynchronous Code is Hard
  110. 110. Testing Asynchronous Code is Hard • • • •
  111. 111. Testing Asynchronous Code is Hard • • • • •
  112. 112. Marble Testing
  113. 113. Marble Testing • Marble Diagram • • 100% • RxJS •
  114. 114. Observable.interval(10) .take(3) .map(x !=> x + 1) .filter(x !=> x % 2 %%=== 1) -01234.. -012| -1-(3|) -01(2|) -12(3|)
  115. 115. 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 }); });
  116. 116. https://youtu.be/i2A1S9o7ZFQ
  117. 117. Programming is thinking, not typing – Cassey Pottan
  118. 118. Be a Programmer, not just a Coder

×