Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
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
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
PyConJP2026_wat_Python × Signal Processing: How to Draw Pictures with Sound Using Spectrogram Art
wat
0
680
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
140
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
760
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
140
MIZARU@SPAJAM2026 第二回予選
1901drama
0
110
Press start. Python's next generation.
willingc
PRO
3
320
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
190
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
130
XP祭りでしか伝わらないフリップネタ #xpjug
murabayashi
0
110
FastAPI の並行処理モデルを完全に理解する
hoto17296
9
3.8k
PyO3 で既存 Python 評価器を Rust core 化する ー wasm-bindgen でブラウザにも配るための設計
kdash
1
560
What We Talk About When We Talk About XP
m_seki
2
440
Featured
See All Featured
[RailsConf 2023] Rails as a piece of cake
palkan
59
7k
Accessibility Awareness
sabderemane
1
200
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
820
Amusing Abliteration
ianozsvald
1
290
What's in a price? How to price your products and services
michaelherold
247
13k
Paper Plane
katiecoart
PRO
2
53k
Automating Front-end Workflow
addyosmani
1369
210k
Speed Design
sergeychernyshev
33
2.1k
The Invisible Side of Design
smashingmag
301
52k
BBQ
matthewcrist
89
10k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
420
Git: the NoSQL Database
bkeepers
PRO
432
67k
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?