Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RxJS - The Art of Abstraction
Search
Jerry Hong
November 01, 2019
Programming
2.7k
5
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
RxJS - The Art of Abstraction
2019/11/1 F2E & JS.tw 小聚
https://blog.jerry-hong.com/speaking/rxjs-the-art-of-abstraction/
Jerry Hong
November 01, 2019
More Decks by Jerry Hong
See All by Jerry Hong
從 Functional Programming 的角度看 2021 的 TypeScript
s6323859
2
4.6k
Abstract Thinking - 從 Functional Programming 看見程式之美
s6323859
4
3.1k
RxJS - 封裝程式的藝術
s6323859
0
1.7k
如何「畫圖」寫測試 - RxJS Marble Testing
s6323859
0
1.3k
Universal JavaScript
s6323859
2
710
Other Decks in Programming
See All in Programming
一参加者から『中の人』へ 〜全通PHPerがブースに立って学んだ、カンファレンスを100倍楽しむコツ〜
wp_daisuke
0
120
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
140
Deep dive into the select statement (GopherCon UK)
jespino
0
170
Building an Out-of-Order CPU
latte72
1
710
Webエンジニアなのにブラウザの仕組みがわからないので、Pythonで自作してみた
tatsuki12
4
1.7k
AIエージェント時代のコードレビューを設計する
nogu66
6
2.5k
AIを上手に使っていこうとしたら越境せざるを得なくなった話 〜実践1年で見えた境界を越えなければならない理由と進め方〜 / Crossing borders with AI
tomoyakitaura
4
1.1k
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
0
450
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
870
kubernetes コンポーネント開発入門 / 新卒N年目の勉強会&交流会!〜〇〇への誘い〜 #n_study
mazrean
0
170
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
130
不幸な GC
chencmd
0
890
Featured
See All Featured
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
430
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.5k
Music & Morning Musume
bryan
47
7.4k
Become a Pro
speakerdeck
PRO
31
6.2k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
310
Docker and Python
trallard
47
4.2k
Why Our Code Smells
bkeepers
PRO
340
58k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Paper Plane (Part 1)
katiecoart
PRO
1
11k
Transcript
RxJS The Art of Abstraction
Jerry Hong Tech Leader | Website: blog.jerry-hong.com Facebook: J.H.blog.tw Branch8
2019 ModerWeb speaker 2018 FEDC organiser 2017 JSDC.tw speaker 2017 RxJS 30天鐵⼈人賽冠軍 2016 JSDC.tw speaker
What’s RxJS ?
Lodash for async
⼀一個透過 Observable 組合各種非同步⾏行行為的 Library
Why RxJS ?
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; }); } });
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; }); } }); 無限滾動 Infinite scroll
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; 註冊滾動事件 Listen scroll event
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; }); 判斷滾動⾼高度 Determine scroll height
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
設定 Flag let isRequesting = false; scrollView.addEventListener('scroll', event = const
DOM = event.target; if(hasScrolled(DOM) > 0.9 && !isRequesting) isRequesting = true; fetch('url...') .then(res => {
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
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
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
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; }); } }); 無限滾動 Infinite scroll
What’s wrong ?
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; }); } }); 兩兩個非同步⾏行行為
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
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; }); } });
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);
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); 我們⼀一定要寫 這麼醜的程式碼嗎?
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); fromEvent(scrollView, 'scroll') .pipe( map(event "=> event.target), map(hasScrolled), filter(p "=> p > 0.9), exhaustMap(() "=> ajax('url""...')), take(3) ) .subscribe(res "=> { "// do something change view }) VanillaJS RxJS
Observable 簡介 Observable 簡介
What is Observable ?
What is Observable ? Collection over Time
Observable 就像是⼀一個序列列, 裡⾯面的元素會隨著時間推送 What is Observable ?
var mouseMove = fromEvent(DOM, 'mousemove'); observable 建立與訂閱 var subscription =
mouseMove .subscribe(x => console.log(x)); subscription.unsubscribe();
of(2, 3, 4); from([2, 3, 4]); ajax('url'); fromEvent(DOM, 'click'); interval(1000);
建立 observable
import { from } from 'rxjs'; import { map, filter
} from 'rxjs/operators'; var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
observable • Observable 的物件實例例 • 在未被訂閱之前,只是 個物件,不會有任何 Side Effect •
可被訂閱(subscribe) import { from } from 'rxjs'; import { map, filter } from 'rxjs/operators var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {},
operator • ⼀一個 function 傳入 observable 回傳 observable • 可對元素作運算處理理
import { from } from 'rxjs'; import { map, filter } from 'rxjs/operators var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
observer • ⼀一個 object • 具有 next, error, complete •
⽤用來來訂閱 observable map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
subscription • observable 訂閱後回傳 的物件 • 可拿來來退訂 (unsubscribe) • 可以其他
subscription 合併 import { from } from 'rxjs'; import { map, filter } from 'rxjs/operators var sub = from([1, 2, 3]) .pipe( map(x "=> x + 1), filter(x "=> x % 2 ""=== 0) ); .subscribe({ next: x "=> console.log(x), error: err "=> {}, complete: () "=> {}, });
None
Marble Diagram --a--b--c--d--e|
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|)
of(1, 2, 3) (123|)
interval(10) - -012 -01 -0 -0123 -01234 -01234..
fromEvent(DOM, 'click') ---e--ee-e--e-...
interval(10) .pipe( take(3), map(x "=> x + 1), filter(x "=>
x % 2 ""=== 1) ) -01234.. -012| -1-(3|) -01(2|) -12(3|)
--0-1-2-3-4-5-6-7.. -------e----------- --0-1-2| interval(20) .pipe( takeUntil( fromEvent(DOM, 'click') ) )
fromEvent(DOM, 'click') .pipe( map(() "=> ajax('url""...')) mergeAll() ) -----e-e---- -----o-o----
\ \ \ ----r| ----r| ---------r-r-
fromEvent(DOM, 'click') .pipe( mergeMap(() "=> ajax('url""...')) ) -----e-e---- -----o-o---- \
\ \ ----r| ----r| ---------r-r-
fromEvent(DOM, 'click') .pipe( switchMap(() "=> ajax('url""...')) ); -----e-e---- -----o-o---- \
\ \ ----r| ----r| -----------r-
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| ----r| fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| --! fromEvent(DOM, 'click') .pipe(
switchMap(() "=> ajax('url""...')) );
-----e-e---- -----o-o---- \ \ \ ----r| --! -----------r- fromEvent(DOM, 'click')
.pipe( switchMap(() "=> ajax('url""...')) );
fromEvent(DOM, 'click') .pipe( exhaustMap(() "=> ajax('url""...')) ) -----e-e---- -----o-o---- \
\ \ ----r| ----r| ---------r---
fromEvent(scrollView, 'scroll') .pipe( map(event "=> event.target), map(hasScrolled), filter(p "=> p
> 0.9), exhaustMap(() "=> ajax('url""...')), take(3) ) .subscribe(res "=> { "// do something change view })
Change Your Thoughts
Before You Learn RxJS
Make a HTTP request Listen an event Read a
file Promise Callback Stream
After You Learn RxJS
Make a HTTP request Listen an events Read a
file A set of values A set of values A set of values
A set of values A set of values A set
of values Observable Make a HTTP request Listen an events Read a file
What’s matter?
物件拖拉 Drag&Drop Drag Drop
mouseDown$
mouseDown$ mouseDown$.pipe( switchMap(() "=> mouseMove$)) )
mouseDown$.pipe( switchMap(() "=> mouseMove$)) )
mouseDown$.pipe( switchMap(() "=> mouseMove$.pipe(takeUntil(mouseUp$)) ) )
mouseDown$.pipe( switchMap(() "=> mouseMove$.pipe(takeUntil(mouseUp$)) ) ) .subscribe(value "=> { "//
do something });
None
mouseClick$
mouseClick$.pipe( switchMap(() "=> request$) ) mouseClick$
mouseClick$.pipe( switchMap(() "=> request$) )
mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ) )
mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ); ); .subscribe((value) "=> { "//
do something })
mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ); ); .subscribe((value) "=> { "//
do something })
mouseClick$.pipe( switchMap(() "=> request$.pipe(takeUntil(cancel$)) ); ); .subscribe((value) "=> { "//
do something }) mouseDown$.pipe( switchMap(() "=> mouseMove$.pipe(takeUntil(mouseUp$)) ) ) .subscribe(value "=> { "// do something }); 取消請求 Cancel Request 拖拉 D&D
Same Logic, Same Code
Everything is set of values
HTTP Server
Requests -> Responses
None
const helloEffect$: HttpEffect = req$ "=> req$.pipe( mapTo({ body: 'Hello,
world!' }), );
const postUser$ = r.pipe( r.matchPath('/user'), r.matchType('POST'), r.useEffect(req$ "=> req$.pipe( map(req
"=> req.body as User), mergeMap(Dao.postUser), map(response "=> ({ body: response })) )));
User Actions
Action -> Side Effects
None
const pingEpic = action$ "=> action$.pipe( filter(action "=> action.type ""===
'PING'), delay(1000), "// Asynchronously wait 1000ms then continue mapTo({ type: 'PONG' }) );
const incrementIfOddEpic = (action$, state$) "=> action$.pipe( ofType(INCREMENT_IF_ODD), filter(() "=>
state$.value.counter % 2 ""=== 1), map(() "=> increment()) );
Rx is cross languages!
RxJava RxSwift RxScala RxClojure RxGo RxPy
Thanks
Any Questions?