點燈坊

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

使用 Observable 讀取 API

Sam Xiao's Avatar 2020-06-03

前端的難點之一是讀取 API 時,回傳為 Promise,這是 Asynchronous 行為;若使用 RxJS 可視為 Observable,以 Stream 方式處理 API 回傳資料。

Version

macOS Catalina 10.15.4
WebStorm 2020.1
Vue 2.6.11
Vue-rx 6.2
RxJS 6.5.5

Browser

api000

顯示 titleprice

Data

[
  {
    "id": 1,
    "title": "FP in JavaScript",
    "price": 100,
    "categoryId": 1
  },
  {
    "id": 2,
    "title": "RxJS in Action",
    "price": 200,
    "categoryId": 2
  },
  {
    "id": 3,
    "title": "Speaking JavaScript",
    "price": 300,
    "categoryId": 3
  }
]

API 回傳為以上 array。

Promise

<template>
  <div>
    <ul>
      <li v-for="(x, i) in books" :key="i">
        Title : {{ x.title }}, Price : {{ x.price }}
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios'

let fetchBooks = axios.get('http://localhost:3000/books')
  .then(x => x.data)

let mounted = function() {
  fetchBooks.then(x => this.books = x)
}

export default {
  name:'app',
  data:() => ({
    books:[],
  }),
  mounted,
}
</script>

14 行

let fetchBooks = axios.get('http://localhost:3000/books')
  .then(x => x.data)

使用 Axios 回傳 Promise。

17 行

let mounted = function() {
  fetchBooks.then(x => this.books = x)
}

mounted hook 呼叫 fetchBooks(),由於回傳的是 Promise,必須使用 then() 與 side effect 將資料寫進 books data,才能顯示在 HTML template。

由於要寫進 data,必須使用 this,因此 mounted() 只能使用 function expression,而不能使用 arrow function。

第 4 行

<li v-for="(x, i) in books" :key="i">
  Title : {{ x.title }}, Price : {{ x.price }}
</li>

v-for directive 支援 books data,可順利顯示在 HTML template。

Observable

<template>
  <div>
    <ul>
      <li v-for="(x, i) in books$" :key="i">
        Title : {{ x.title }}, Price : {{ x.price }}
      </li>
    </ul>
  </div>
</template>

<script>
import { ajax } from 'rxjs/ajax'
import { pluck } from 'rxjs/operators'

let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
  pluck('response')
)

let subscriptions = () => {
  let books$ = fetchBooks$

  return { books$ }
}

export default {
  name:'app',
  subscriptions,
}
</script>

15 行

let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
  pluck('response')
)

Axios 回傳 Promise,直接使用 RxJS 所提供的 ajax() 回傳 Observable。

實務上若 function 回傳 Observable,function 名稱會加上 $ postfix

pipe()
將多個 unary function 組合成新的 function

由於資料都放在 Promise 的 Response.data 下,而我們真正想要的資料又放在 books 下,因此使用 pluck()data.books 擷取出想顯示的資料。

pluck()
從 object 中擷取指定 nested property

api001

20 行

let books$ = fetchBooks$

定義 books$ Observable,由於 fetchBooks$ 已經是 Observable,因此可直接指定。

22 行

return { books$ }

Vue 的 HTML template 並不支援 Promise,因此我們只能在 then() 以 side effect 寫入 data,但卻支援 Observable,只要直接在 subscription() 回傳 Observable 即可。

第 4 行

<li v-for="(x, i) in books$" :key="i">
  Title : {{ x.title }}, Price : {{ x.price }}
</li>

v-for 部分不變,唯從 books data 改成 books$ Observable。

v-for directive 也能順利支援 Observable。

Conclusion

  • 使用 Promise 必須搭配 data 才能顯示在 HTML template,因此勢必使用 this
  • Observable 可直接在 HTML template 顯示,不須搭配 data,也因此不用使用 this,可以讓 Vue 更接近 FP 風格,不用等 Vue 3 的 composition API 就可完全擺脫 this
  • v-for directive 完美支援 Observable,因此 HTML template 部分寫法不變

Reference

John Lindquist, Stream an API using RxJS into a Vue.js Template
RxJS, pipe()
RxJS, pluck()