123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- import {reactive} from 'vue'
- import {createGlobalState, hasOwn} from "@vueuse/core";
- import {empty, func} from "@/uni_modules/uv-ui-tools/libs/function/test";
- import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
- import {getSizeOfObject} from "@/utils";
- import _ from 'lodash';
- function CacheResult(data, options) {
- if (hasOwn(data, 'dataSize')) {
- // 如果对象内部有循环钩子,getSizeOfObject无法计算,则预先缓存大小
- this.data = data.data
- this.dataSize = data.dataSize
- } else {
- this.data = data
- }
- this.expires = options?.timeout ? (options.timeout + Date.now()) : null
- this.obsoleted = () => {
- return this.expires && this.expires < Date.now()
- }
- }
- export const useCacheStore = createGlobalState(() => {
- // data
- const container = reactive(new Map())
- const defaultOptions = {
- timeout: 2 * 3600 * 1000, // default cache 2 hours
- emptyIgnore: false // default cache empty values
- }
- // hooks
- // functions
- const checkActionDefine = (action) => {
- if (!action) throw new Error('action is required')
- if (func(action)) {
- return {
- type: action.toString(),
- handler: async (...args) => await action(...args)
- }
- } else {
- if (!action.type) throw new Error('action.type is required')
- if (!action.handler) throw new Error('action.handler is required')
- if (!func(action.handler)) throw new Error('action.handler must be a function')
- }
- return action
- }
- const generateCacheKey = function (payload) {
- return JSON.stringify({payload})
- }
- const executeAction = async (action, payload) => {
- let result = action.handler(payload)
- if (result instanceof Promise) result = await result
- return result
- }
- /*
- * 获取缓存结果。
- * 虽然改进后缓存动作传入function也可以,但用defineCacheAction的方式调试时更直观。
- * @param {Object|Function} action 缓存动作。action是Function时尤其要注意用payload区隔结果!
- * @param {Object} payload 缓存参数,区隔缓存结果的关键!
- * @param {Object} options 缓存选项
- * @returns {Object} 缓存结果
- * */
- const dispatchCache = async (action, payload = undefined, options = undefined) => {
- // TODO:并发锁, 并发机会不高且没有致命性影响,以后再优化
- const formatAction = checkActionDefine(action)
- let typeNode = container.get(formatAction.type)
- if (!typeNode) {
- typeNode = new Map()
- container.set(formatAction.type, typeNode)
- }
- const key = generateCacheKey(payload)
- let cacheData = typeNode.get(key)
- // fire cached result
- if (cacheData instanceof CacheResult && !cacheData.obsoleted()) {
- // call `sleep` to simulate api reaction.
- // NOTE: 这个模拟对于mx-condition.config.isImmediate有关键作用,否则需要对缓存和API实时做不同配置
- await sleep(0)
- return cacheData.data
- }
- // cache miss, execute action and cache result
- const result = await executeAction(formatAction, payload)
- const cacheOptions = _.merge(defaultOptions, options)
- cacheData = new CacheResult(result, cacheOptions)
- if (!(empty(result) && cacheOptions.emptyIgnore)) {
- typeNode.set(key, cacheData)
- }
- return cacheData.data
- }
- /*
- * 是否存在缓存
- * @param {Object} action 缓存动作
- * @param {Object} payload 缓存参数
- * @returns {Boolean} 是否存在缓存
- * */
- const hasCache = (action, payload) => {
- const formatAction = checkActionDefine(action)
- const typeNode = container.get(formatAction.type)
- if (!typeNode) return false
- const key = generateCacheKey(payload)
- return typeNode.has(key)
- }
- /*
- * 删除缓存
- * @param {Object} action 缓存动作
- * @param {Object} payload 缓存参数
- * */
- const removeCache = (action, payload = null) => {
- if (payload === null || payload === undefined) {
- clearCache(action)
- return;
- }
- const formatAction = checkActionDefine(action)
- const typeNode = container.get(formatAction.type)
- if (!typeNode) return
- const key = generateCacheKey(payload)
- typeNode.delete(key)
- if (!typeNode.size) container.delete(formatAction.type)
- }
- /*
- * 清理缓存
- * @param {Object} action 缓存动作,action = null时清空所有缓存
- * */
- const clearCache = (action = null) => {
- if (!action) {
- container.clear()
- return
- }
- const formatAction = checkActionDefine(action)
- container.delete(formatAction.type)
- }
- /*
- * 清理过期缓存
- * */
- const gcCache = () => {
- if (!container.size) return
- console.log('run useCacheStore: gc')
- for (const [type, typeNode] of container.entries()) {
- for (const [key, cacheData] of typeNode.entries()) {
- if (cacheData.obsoleted()) {
- typeNode.delete(key)
- console.log('run useCacheStore: gc: remove cache', type, key)
- }
- }
- if (!typeNode.size) {
- container.delete(type)
- console.log('run useCacheStore: gc: remove type', type)
- }
- }
- }
- const getSize = function () {
- // then calculate
- let total = 0
- container.forEach(typeNode => {
- typeNode.forEach(i => {
- if (hasOwn(i, 'dataSize')) {
- total += i.dataSize
- } else {
- total += getSizeOfObject(i)
- }
- })
- })
- return total
- }
- // window.cacheContainer = container
- return {
- container, // open for test
- dispatchCache,
- hasCache,
- removeCache,
- clearCache,
- gcCache,
- getSize
- }
- })
|