點燈坊

失くすものさえない今が強くなるチャンスよ

使用 share() 避免重複 API Request

Sam Xiao's Avatar 2020-06-10

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

share000

+ 按下後除了顯示 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。

share001

因為建立 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。

share002

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()