RxJS 因為是 Reactive,因此由 Observable 建立其他 Observable 時,也會重發新 API Request 反應最新資料,若我們想重複使用既有 Observable 且不建立新 API Request,可使用 share()
達成需求。
Version
macOS Catalina 10.15.4
WebStorm 2020.1
Vue 2.6.11
RxJS 6.5.5
Browser
+
按下後除了顯示 FP in JavaScript
外,還同時顯示了其封面圖片。
Data
{
"title": "FP in JavaScript",
"price": 100,
"categoryId": 1,
"image": "fpjs.jpg"
}
http://localhost:3000/books/1
回傳為以上 object。
Mutiple Request
<template>
<div>
<button v-stream:click="{ subject: click$, data: 1 }">+</button>
<h1>{{ title$ }}</h1>
<img :src="image$">
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { map, pluck, exhaustMap } from 'rxjs/operators'
let fetchBook$ = x => ajax(`http://localhost:3000/books/${x}`).pipe(
pluck('response')
)
let subscriptions = function() {
let book$ = this.click$.pipe(
pluck('data'),
exhaustMap(fetchBook$)
)
let title$ = book$.pipe(pluck('title'))
let image$ = book$.pipe(
pluck('image'),
map(x => `/images/${x}`)
)
return { title$, image$ }
}
export default {
name: 'app',
domStreams: ['click$'],
subscriptions,
}
</script>
13 行
let fetchBook$ = x => ajax(`http://localhost:3000/books/${x}`).pipe(
pluck('response')
)
使用 ajax()
呼叫 API 回傳 Observable。
18 行
let book$ = this.click$.pipe(
pluck('data'),
exhaustMap(fetchBook$)
)
book$
為 fetchBook$()
所回傳的 Observable。
22 行
let title$ = book$.pipe(pluck('title'))
let image$ = book$.pipe(
pluck('image'),
map(x => `/images/${x}`)
)
title$
與 image$
皆為由 book$
所產生的 Observable。
因為建立 title$
與 image$
兩個 Promise 而產生了兩次相同 API request,這該如何避免呢 ?
share()
<template>
<div>
<button v-stream:click="{ subject: click$, data: 1 }">+</button>
<h1>{{ title$ }}</h1>
<img :src="image$">
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { map, pluck, exhaustMap, share } from 'rxjs/operators'
let fetchBook$ = x => ajax(`http://localhost:3000/books/${x}`).pipe(
pluck('response')
)
let subscriptions = function() {
let book$ = this.click$.pipe(
pluck('data'),
exhaustMap(fetchBook$),
share()
)
let title$ = book$.pipe(pluck('title'))
let image$ = book$.pipe(
pluck('image'),
map(x => `/images/${x}`)
)
return { title$, image$ }
}
export default {
name: 'app',
domStreams: ['click$'],
subscriptions,
}
</script>
18 行
let book$ = this.click$.pipe(
pluck('data'),
exhaustMap(fetchBook$),
share()
)
在 pipe()
中加上 share()
,如此所有從 book$
所產生的 Observable 將共用同一個 book$
,不會再有另外的 API request。
Conclusion
- 由於 RxJS 的 reactive 特性,常會不小心產生 multiple API request,在 refactoring 階段別忘使用 Chrome 的 DevTool 觀察並加上
share()
Reference
John Lindquist, Share RxJS Streams to Avoid Mutiple Request in Vue.js
RxJS, share()