微信小程式 api 快取方案
微信小程式 api 快取方案
背景
為了應對使用者流量大,減輕伺服器的壓力,減少網路請求次數,加快資料的顯示,以及提高使用者體驗。我們現在需要把一些公共請求進行本地快取,並且提供不同的更新策略給開發者選擇。
前言
與 web 端不同的是,微信小程式並沒有cookie
,localStorage
,sessionStorage
,indexedDB
,Web SQL
(已廢棄),service worker
這些花裡胡哨的東西。只有一套已經封裝好的介面。所以就需要基於微信小程式提供的這套資料快取介面來實現我們的 api 快取方案。
快取方案(更新策略)
這裡我們提供三種快取方案。靈感來源於一次service worker
pwa
應用實踐。方案如下:
- 第一種方案:有快取資料並且沒有過期的情況下,直接返回快取資料,不進行網路請求獲取資料;沒有快取資料或者是有快取資料,但是快取資料的有效時間過期的情況下,傳送網路請求,等待資料返回,然後儲存到本地,並且設定過期時間,最後在把資料返回給前端;資料儲存到本地的時候,大家最好設定一個過期時間,否則這個快取資料就永遠無法得到更新,因為有資料並且沒有過期的情況下是直接返回快取資料的,不會進行網路請求獲取資料,除非使用者手動清除快取。這個方案最大的優點就是減少了網路請求的次數,加快資料的響應速度(有資料的情況下不用等待網路請求的時間),缺點就是資料不能得到及時的更新。在應對一些高併發,使用者流量大的情況下,這個方案會比較好。因為這種情況下,後臺會有屬於自己的快取方案,比如 CDN 快取,nginx 快取,這些快取都會有一個快取時間的,我們只要在本地的快取資料中設定一個大於後臺快取時間的有效時間即可。這樣既可以保證資料能夠被更新,又能減少網路請求的次數。比方說後臺的快取時間是 5 分鐘,前端可以設定過期時間是 6 分鐘,前端不設定 5 分鐘是因為後臺快取的時間並不一定是一個準確數,你在第五分鐘獲取的資料可能還是舊的資料,所以時間上要略大一點。
- 第二種方案:有快取資料並且沒有過期的情況下,直接返回快取資料,同時繼續傳送網路請求,請求的資料回來之後對快取資料進行更新;沒有快取資料或者有快取資料,但是快取資料已經過期的情況下,傳送網路請求,然後儲存到本地,並且設定過期時間,最後再把資料返回給前端;這個方案每次都是需要傳送請求的,然後在更新快取資料的。這裡我建議大家也設定一個過期時間,這樣可以防止資料太舊了,比方說我現在請求了一次網路資料,然後退出了小程式,五天後再開啟小程式,如果快取資料沒有設定過期的情況下,那麼我們看見的將會是五天前的資料。這個方案的優點就是資料能夠及時更新(每次看見的資料都是上一次請求回來的資料),加快資料的響應時間(有資料的情況下不用等待網路請求的時間),缺點就是不能減少網路請求的的次數。這個方案適用於一些要求資料及時性比較高或者網路情況比較差的情景,比如網路比較慢的時候,一個請求需要 5 秒時間,在有快取資料的情況下,會直接返回快取資料,由於網路請求是非同步的,5 秒後請求回來之後會自動更新快取資料,這並不影響後面的程式碼執行。
- 第三種方案:當沒有網路或者請求報錯,並且有快取資料的情況下,就返回快取資料,沒有快取資料就返回空。這種方案每次請求都會對快取資料進行更新。優點就是提高系統的健壯性和可用性,不至於沒有網路或者後臺報錯的時候什麼資料都沒有,前端頁面一片空白,這樣就可以大大的提高使用者的體驗。缺點就是你需要區分哪些資料是成功請求返回來的,哪些資料是失敗請求返回來的,因為失敗的請求還需要給個提示使用者那裡出錯了。
- 第四種方案:不進行快取,並不是所有的介面都需要快取,有些需要保證前端和後臺的資料保持一致性的,不然可能會導致資料庫的修改丟失。比方說我修改使用者名稱,張三改成李四,但是由於快取的原因,我再次修改的時候顯示的還是張三,然後直接點選儲存,資料庫儲存的又變回張三了,張三改成李四這個修改的操作就會丟失了。
快取那些介面
快取方案有了,那麼我們需要快取那些介面呢。一般來說,我們的介面會分為三種類型:public,my,protect。
- public:一般是公共介面,用於公共頁面的,就是那些遊客可以看見的就是公共頁面,通常來說是列表介面來的。請求方法是
get
,後臺會有快取。 - protect:一般來說是需要使用者登入之後才能呼叫的介面,介面需要根據使用者的資訊才能出資料的,還有就是這些介面是需要許可權的。通常就是用於只有登入會後才能看見的一些頁面。請求方法是
post
,因為需要帶上 token。protect 型別的介面後臺也會進行快取 - my:一般用於個人空間那些模組,就是需要編輯,刪除或者新增的模組,這些操作都需要資料及時更新,後臺不會進行快取,直接走資料庫。
上面有三種類型的介面,我們只快取public
型別的介面;my
型別的介面由於要求資料的及時性比較高,所以不做快取;至於protect
型別的介面也不做快取,因為介面資料是跟使用者資訊(token)有關的,不同的使用者呼叫同樣的介面,返回的資料是不一樣的,並且快取資料的時候我們都是根據介面 url+請求引數作為key
值。當然,如果想要快取protect
型別的介面也是可以的,我們給介面加上對應的標識(userId 等,最好不要用 token 這些,因為會經常變化,導致命中不了快取),標識這個介面對應的是哪個使用者就可以了,這就要求我們實現的快取工具類需要有一個可以自定義快取的key
值的功能,讓開發者自己去定義key
值。
Storage 快取工具類
基於上面的描述,我們首先需要封裝一個操作快取資料的工具類,需要有如下核心功能:
1、獲取資料
get(key:string,def?:any)
根據傳入的key
值獲取資料,def
是沒有資料的時候預設返回來的資料。獲取資料的時候還需要判斷這個資料有沒有過期,過期就需要刪除資料,並且返回null
或者def
2、設定資料
set(key:string, val:any, options?:Object)
按照key-val
的形式儲存資料,options
是其他一些配置引數,比如設定有效期時長
3、刪除最近最少使用的項的快取物件
我們需要考慮的一個問題就是,快取的資料量可能會比較多,但是儲存的空間不夠(微信小程式最大可以快取 10MB 資料,這種情況一般不會發生)或者只想快取 10 條請求,超出的部分需要首先淘汰掉一部分已經儲存起來的資料,然後再把超出的部分儲存進去。淘汰演算法使用最近最少使用的。思路如下:
- 新建一個數組,用來儲存快取資料的
key
值 get
獲取資料的時候,將陣列中對應的key
值放到最後一位,這樣可以保證最近使用過的在最後一位,最近最少使用的就放在首位。set
設定資料的時候,首先檢查陣列中是否已經包含了對應的key
值,包含就刪除。最後把key
值放置到陣列中最後一位。
實現:
class Storage {
// 設定值
set(key, val, options) {
// 將使用者傳入的配置和預設配置進行合併
const config = Object.assign({}, defaultConfig, options || {});
// 判斷是否超出儲存長度,超出則刪除最近最少使用的項的快取物件
isOverLimitSize(this.maxSize);
// 將需要儲存的資料轉化為我們需要的格式
const data = initData(val, config);
// 儲存資料
wx.setStorageSync(key, data);
// 儲存key值到陣列中
addCacheKey(key);
return val;
}
// 獲取值
get(key, def) {
// 根據key值獲取value值
const val = wx.getStorageSync(key);
if (isNotDefine(val)) {
// 如果是空值則返回預設值
return def;
}
// 檢查格式是否正確,因為有些資料不是通過這個工具類儲存的
if (isFlag(val)) {
// 檢查資料有效期
const isExpire = checkExpire(val);
if (!isExpire) {
// 沒過期,修改key值的儲存位置
addCacheKey(key);
return val.data;
}
// 過期,則刪除快取資料
this.remove(key);
return def;
}
// 修改key值的位置
addCacheKey(key);
return val;
}
// 根據key移除快取
remove(key) {
// 將key值從陣列中刪除
deleteCacheKey(key);
return wx.removeStorageSync(key);
}
// ... 其他功能
}
ApiCache 類實現
配置
我們需要有一些配置給使用者選擇,比如選擇什麼快取方案,那些介面需要快取等
- cache:false 就是禁止進行快取。1 就是上述的第一種快取方案。2 就是上述第二種快取方案。預設是 1。
- cacheKey:函式,快取資料的
key
值,該函式會有 2 個引數,一個是請求配置,一個是快取配置。預設返回介面 url+請求引數。 - shouldCache:函式,判斷介面是否需要進行快取,返回 true 就是快取,false 就是不快取。該函式會有 2 個引數,一個是請求配置,一個是快取配置。預設快取
get
請求。
封裝 request 請求
這裡使用的請求庫是我自己封裝的一個基於Promise
的 Http 請求庫,參考了axios
原始碼的設計思想。有興趣的同學可以點選這裡。這裡封裝 request 請求就是基於這個庫,然後加一層快取下去的。
實現:
let defaultCacheConfig = {
// cache: false-不快取;1-本地有資料就使用本地資料,不請求;2-本地有資料就返回本地資料,然後請求資料回來之後更新快取
cache: 1,
cacheKey(requestConfig, cacheOptions) {
return requestConfig.url+queryString(requestConfig.data);
},
shouldCache(requestConfig, cacheOptions) {
return requestConfig.method === 'get';
}
};
function ApiCache() {}
ApiCache.prototype.request = function (method, url, config, cacheOptions) {
// 合併請求配置
const requestConfig = Object.assign({}, config || {}, {
method,
url,
});
// 合併快取配置
const cacheConfig = Object.assign({}, defaultCacheConfig, cacheOptions);
if (
cacheConfig.cache &&
cacheConfig.shouldCache(requestConfig, cacheOptions)
) {
// 開啟快取
const cacheKey = cacheConfig.cacheKey(requestConfig, cacheOptions);
const cacheValue = storage.get(cacheKey);
if (cacheValue) {
// 有快取值得時候
if (cacheConfig.cache === 2) {
// 方案2需要傳送請求更新快取資料
request
.request(requestConfig)
.then((res) => {
storage.set(cacheKey, res, cacheConfig);
})
.catch(() => {});
}
return Promise.resolve(cacheValue);
} else {
// 不存在快取值得時候,通過請求獲取資料
return request
.request(requestConfig)
.then((res) => {
storage.set(cacheKey, res, cacheConfig);
return Promise.resolve(res);
})
.catch((error) => {
return Promise.reject(error);
});
}
} else {
// 沒有開始快取的
return request.request(requestConfig);
}
};
總結
到此,微信小程式 api 快取方案已經實現完畢了。實現起來並不是很難,難點就在於快取方案的設計上面,怎麼做到快速響應資料,怎麼更新資料,那些介面需要快取,那些介面不需要快取等等這些都是需要我們去考慮的。當然這套方案不僅可以用於微信小程式中,也可以用在web端的,實現起來大同小異。如果有興趣想了解一下這方面的知識,可以關注我的開源專案微信小程式元件庫,裡面包含了一些擴充套件功能,用來解決一些微信小程式的業務場景。
歡迎掃碼體驗