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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
3k
RxJS - 封裝程式的藝術
s6323859
0
1.7k
如何「畫圖」寫測試 - RxJS Marble Testing
s6323859
0
1.2k
Universal JavaScript
s6323859
2
690
Other Decks in Programming
See All in Programming
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
190
運用エージェントは "作る" から "育てる" へ - 記憶と自己進化の3層設計パターン / self-evolving-agents-three-layer-agent-design
gawa
12
3.6k
TAKTでAI駆動開発の品質を設計する
j5ik2o
6
1.2k
A2UI という光を覗いてみる
satohjohn
1
130
タクシーアプリ『GO』の バックエンド開発のおける AI利活用と若者のすべて
pyama86
3
2k
Spring Security 実践 ─ GraphQL APIで実務に役立つ 認証・認可 を学ぶ
wagyu
0
220
Agentic UI
manfredsteyer
PRO
0
140
AI駆動開発で崩れていくコードベースを立て直す
kyoko_nr_nr
1
450
AIチームを指揮するOSS「TAKT」活用術 / How to Use “TAKT,” an OSS Tool for Orchestrating AI Teams
nrslib
6
880
Copilot CLI の継戦能力を高める コンテキスト管理
nozomutu
1
1.2k
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
680
Featured
See All Featured
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.6k
A better future with KSS
kneath
240
18k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
270
Agile that works and the tools we love
rasmusluckow
331
21k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
410
Java REST API Framework Comparison - PWX 2021
mraible
34
9.4k
sira's awesome portfolio website redesign presentation
elsirapls
0
280
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
940
Code Review Best Practice
trishagee
74
20k
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?