如何對 API 回傳資料加上 Cache ?
若 API 每次傳入的 Query String 相同,所回傳的結果也相同,同時也沒對資料庫做任何異動,對於這類 API,其實可以加上 Cache,當引數相同時,就不再向後端打 API,如此不但節省後端資源,前端的反應也更為迅速,使用者體驗更佳。
事實上 ECMAScript 的 Closure + IIFE 就能實作出 Cache,並不需要依賴其他 Package。
Version
Vue 2.5.22
Vue CLI 3.4.0
Axios 0.18.0
Component
components/HelloWorld.vue
<template> <div> ID:<input type="text" v-model="id"/> <button @click="onSubmit">Submit</button> <p></p> {{ title }} </div> </template> <script> import fetchBook from '@/api/book-info'; const onSubmit = function() { fetchBook({ id: this.id }) .then(res => this.title = res.data[0].title); }; export default { name: 'HelloWorld', data: () => ({ id: null, title: '', }), methods: { onSubmit, }, }; </script>
13 行
const onSubmit = function() { fetchBook({ id: this.id }) .then(res => this.title = res.data[0].title); };
當使用 GET query string 方式時,Axios 要求我們傳入一個 object,因此組了 { id: this.id }
,由於 fetchBook()
會回傳 Promise
,因此使用 then()
方式寫入 title
data。
API Function
api/book-info.js
import axios from 'axios'; const API = process.env.VUE_APP_API; const URI = process.env.VUE_APP_BOOK_INFO; export default args => axios.get(`${API}${URI}`, { params: args });
API 部分將傳入的 args
object,再組一個 key 為 params
的 object 傳入 Axios.get()
。
這是最普通使用 GET query string 方式,每次呼叫都會重打一次 API。
若 API 具有 Pure Function 特性,也就是每次呼叫時,只要 Query String 相同,結果就相同,且資料庫也不會有所異動 (無 Side Effect),此時就可以將 API function 加以 cache,如此將大幅增加前後端執行效率。
Cacheable
api/book-info.js
import axios from 'axios'; const API = process.env.VUE_APP_API; const URI = process.env.VUE_APP_BOOK_INFO; const toKey = args => Object.values(args).join('_'); const fn = args => axios.get(`${API}${URI}`, { params: args }); export default (() => { const storage = {}; return args => { const key = toKey(args); return (storage[key]) ? storage[key] : storage[key] = fn(args); }; })();
要實作 cache 其實並不需要額外 package,只要使用 ECMAScript 語言特性:Closure + IIFE 即可達成。
第 7 行
const fn = args => axios.get(`${API}${URI}`, { params: args });
將原本的 args => axios.get()
另外定義成 fn()
。
第 9 行
export default (() => { ... return args => { ... }; })();
原本是 export function,現在改成 export IIFE,且 IIFE 回傳引數為 args
的新 function。
10 行
const storage = {};
Cache 的關鍵就在此:在 IIFE 內宣告 storage
object。
storage
第 6 行
const toKey = args => Object.values(args).join('_');
要能使用 cache,key 是關鍵因素,特別建立了 toKey()
產生 key。
由於 args
為 object,先使用 Object.values()
轉成 array,再使用 Array.join()
產生中間以 _
分隔的 string 作為 key。
14 行
return (storage[key]) ? storage[key] : storage[key] = fn(args);
若該 key 存在於 storage
object,則直接回傳 storage[key]
,內部存的是 Promise。
若不存在,則呼叫 fn()
打 API,先存到 storage
object 後再回傳。
當然也可以將 value 存進 storage
object,由於新 function 回傳的是 Promise
,若 cache 內找到 value,還必須自行 new Promise()
,若找不到則還需 fn(args).then()
處理,比較麻煩
目前資料只有 ID
1 到 3,所以前三筆要打 API,之後再重複輸入相同 ID
,就直接從 cache 取出了。
Higher Order Function
api/book-info.js
import axios from 'axios'; import { cache } from '../helpers/promise'; const API = process.env.VUE_APP_API; const URI = process.env.VUE_APP_BOOK_INFO; const fn = args => axios.get(`${API}${URI}`, { params: args }); export default cache(fn);
基本的機制都有了,剩下就是 reuse 問題,是否能將這種 cache 機制抽成 cache()
Higher Order Function 呢 ? 將來任何 function 只要經過 cache()
包裝過,就變成 Cacheable Function。
helpers/promise.js
const toKey = args => Object.values(args).join('_'); export const cache = fn => (() => { const storage = {}; return args => { const key = toKey(args); return (storage[key]) ? storage[key] : storage[key] = fn(args); }; })();
將 toKey()
重構到 helpers/promise.js
內,且將原本在 api/book-info.js
的 code 抽成 cache()
Higer Order Function。
由於 cache 的儲存與判斷機制都相同,只有 axios.get()
的 fn()
不同,因此將 fn()
抽成 fn()
parameter,由 api
function 傳入。
如此任何 api
function 只要經過 cache()
包裝過,就成了 Cacheable Function,大大提高開發效率。
Conclusion
- Closure 天生就能夠 cache,只要善用 ECMAScript 語言特性就能實作 cache
- 可將 Promise 直接存入
storage
object,可避開自行建立Promise
- 對於 Code Reuse 部分,OOP 會將相同部分使用繼承,或 Extract Class 之後再 DI 注入;FP 則會將相同部分抽成 Higher Order Function,相異部分傳入 callback function
Sample Code
完整的範例可以在我的 GitHub 上找到