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
AmebaブログはKMPから卒業します
kmars0605
0
210
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
300
AIに “答え“を書かせるのを やめてみた
___akari11
0
120
[ハンズオン]AIへの指示だけで「五目並べ」を作ってみよう
satoshi256kbyte
1
380
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
28
33k
現場と育てるRailsモデリング 〜Spreadsheetから始めたモデル設計〜
siroemk
2
690
AIで開発が速くなった先で、開発チームは何を目指すか
shihochan
1
1.7k
Compiling a real-world CLI tool to native with Static Hermes
tmikov2023
0
130
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
170
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
820
自サークルのIdPからログアウトできるようになるまで
taiseiue
1
140
Featured
See All Featured
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.5k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
1k
Code Reviewing Like a Champion
maltzj
529
40k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
450
Tell your own story through comics
letsgokoyo
1
1.1k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
How to build a perfect <img>
jonoalderson
1
6.1k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
760
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
600
Abbi's Birthday
coloredviolet
4
10k
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?