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
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.7k
Abstract Thinking - 從 Functional Programming 看見程式之美
s6323859
4
3.1k
RxJS - 封裝程式的藝術
s6323859
0
1.8k
如何「畫圖」寫測試 - RxJS Marble Testing
s6323859
0
1.3k
Universal JavaScript
s6323859
2
720
Other Decks in Programming
See All in Programming
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
360
コードで差別化できない時代の ソフトウェアエンジニアリング / Software Engineering in an Era Where Code Doesn’t Differentiate
kishida
2
130
GitHubハンズオン講座 — 実務レベルのチーム開発のフローを身につけよう
junhat6
0
130
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
520
AIに “答え“を書かせるのを やめてみた
___akari11
0
110
Workers Cache を知る
syumai
0
350
Rでドレミ/Do-Re-Mi_with_R
florets1
0
130
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
280
Embedded Swiftで作る自作USBデバイス によるiOSデバイスの自動テスト / iOSDC Japan 2026 glassfiber
glassfiber
0
310
SalesForceを内製化!? ~ HR事業を支える顧客管理基盤のインフラを大公開 ~
oku053
0
160
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
350
最近”作らせてる” ローカルAIアプリの話
akit37
0
430
Featured
See All Featured
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
450
[SF Ruby Conf 2025] Rails X
palkan
3
1.4k
The Cult of Friendly URLs
andyhume
79
7k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
400
Building the Perfect Custom Keyboard
takai
2
900
Designing for Timeless Needs
cassininazir
1
520
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.8k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
770
Automating Front-end Workflow
addyosmani
1369
210k
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?