點燈坊

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

使用 exhaustMap() 避免重複 API Request

Sam Xiao's Avatar 2020-06-10

為避免 User 產生重複 API Request,將 Button Disabled 是一種方式,也可改用 exhaustMap() 實現。

Version

macOS Catalina 10.15.4
WebStorm 2020.1
Vue 2.6.11
RxJS 6.5.5

switchMap()

pause000

未使用 disabled button,因此 user 的每一個 click 都會產生新的 API request。

<template>
  <div>
    <button v-stream:click="{ subject: click$, data: 1 }">Show book</button>
    <h1>{{ title$ }}</h1>
    <img :src="image$">
  </div>
</template>

<script>
import { ajax } from 'rxjs/ajax'
import { delay, map, pluck, share, switchMap } from 'rxjs/operators'

let fetchBook$ = x => ajax(`http://localhost:3000/books/${x}`).pipe(
  delay(10000),
  pluck('response')
)

let subscriptions = function() {
  let book$ = this.click$.pipe(
    pluck('data'),
    switchMap(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>

13 行

let fetchBook$ = x => ajax(`http://localhost:3000/books/${x}`).pipe(
  delay(10000),
  pluck('response')
)

Delay 10 秒鐘模擬慢速網路。

19 行

let book$ = this.click$.pipe(
  pluck('data'),
  switchMap(fetchBook$),
  share()
)

直覺會使用 switchMap() 實現。

exhaustMap()

pause001

當 API 尚未完全回傳資料時,無論 user 按幾次 click 都不會產生新的 API request,這才是我們所要的。

<template>
  <div>
    <button v-stream:click="{ subject: click$, data: 1 }">Show book</button>
    <h1>{{ title$ }}</h1>
    <img :src="image$">
  </div>
</template>

<script>
import { ajax } from 'rxjs/ajax'
import { delay, map, pluck, share, exhaustMap } from 'rxjs/operators'

let fetchBook$ = x => ajax(`http://localhost:3000/books/${x}`).pipe(
  delay(10000),
  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>

19 行

let book$ = this.click$.pipe(
  pluck('data'),
  exhaustMap(fetchBook$),
  share()
)

swtichMap() 換成 exhaustMap() 問題就解決了。

pause002

135 可視為每次 user click,101010 可視為每次 API 回傳資料,要傳 3 個 10 才算回傳完全。

關鍵在第三次按 5 是在第二次 API 尚未回傳完全時按下,switchMap() 會取消原本傳輸而發起新的 API request,這就是 switchMap() 造成重複 API request 主因,且第二次傳輸的資料不完全也是錯的。

pause003

exhaustMap() 則不然,因為第二次 API 尚未回傳完全,因此拒絕了 user 第三次按 5 所發起的新 API request,所以不會有重複 API request,且資料都是完整的。

Conclusion

  • exhaustMap() 在實務上經常使用,除了不會造成重複 API request 外,也能確保每次都是回傳完整資料,不會中途放棄而造成資料錯誤

Reference

RxJS, switchMap()
RxJS, exhaustMap()