useCacheStore.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import {reactive} from 'vue'
  2. import {createGlobalState, hasOwn} from "@vueuse/core";
  3. import {empty, func} from "@/uni_modules/uv-ui-tools/libs/function/test";
  4. import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
  5. import {getSizeOfObject} from "@/utils";
  6. import _ from 'lodash';
  7. function CacheResult(data, options) {
  8. if (hasOwn(data, 'dataSize')) {
  9. // 如果对象内部有循环钩子,getSizeOfObject无法计算,则预先缓存大小
  10. this.data = data.data
  11. this.dataSize = data.dataSize
  12. } else {
  13. this.data = data
  14. }
  15. this.expires = options?.timeout ? (options.timeout + Date.now()) : null
  16. this.obsoleted = () => {
  17. return this.expires && this.expires < Date.now()
  18. }
  19. }
  20. export const useCacheStore = createGlobalState(() => {
  21. // data
  22. const container = reactive(new Map())
  23. const defaultOptions = {
  24. timeout: 2 * 3600 * 1000, // default cache 2 hours
  25. emptyIgnore: false // default cache empty values
  26. }
  27. // hooks
  28. // functions
  29. const checkActionDefine = (action) => {
  30. if (!action) throw new Error('action is required')
  31. if (func(action)) {
  32. return {
  33. type: action.toString(),
  34. handler: async (...args) => await action(...args)
  35. }
  36. } else {
  37. if (!action.type) throw new Error('action.type is required')
  38. if (!action.handler) throw new Error('action.handler is required')
  39. if (!func(action.handler)) throw new Error('action.handler must be a function')
  40. }
  41. return action
  42. }
  43. const generateCacheKey = function (payload) {
  44. return JSON.stringify({payload})
  45. }
  46. const executeAction = async (action, payload) => {
  47. let result = action.handler(payload)
  48. if (result instanceof Promise) result = await result
  49. return result
  50. }
  51. /*
  52. * 获取缓存结果。
  53. * 虽然改进后缓存动作传入function也可以,但用defineCacheAction的方式调试时更直观。
  54. * @param {Object|Function} action 缓存动作。action是Function时尤其要注意用payload区隔结果!
  55. * @param {Object} payload 缓存参数,区隔缓存结果的关键!
  56. * @param {Object} options 缓存选项
  57. * @returns {Object} 缓存结果
  58. * */
  59. const dispatchCache = async (action, payload = undefined, options = undefined) => {
  60. // TODO:并发锁, 并发机会不高且没有致命性影响,以后再优化
  61. const formatAction = checkActionDefine(action)
  62. let typeNode = container.get(formatAction.type)
  63. if (!typeNode) {
  64. typeNode = new Map()
  65. container.set(formatAction.type, typeNode)
  66. }
  67. const key = generateCacheKey(payload)
  68. let cacheData = typeNode.get(key)
  69. // fire cached result
  70. if (cacheData instanceof CacheResult && !cacheData.obsoleted()) {
  71. // call `sleep` to simulate api reaction.
  72. // NOTE: 这个模拟对于mx-condition.config.isImmediate有关键作用,否则需要对缓存和API实时做不同配置
  73. await sleep(0)
  74. return cacheData.data
  75. }
  76. // cache miss, execute action and cache result
  77. const result = await executeAction(formatAction, payload)
  78. const cacheOptions = _.merge(defaultOptions, options)
  79. cacheData = new CacheResult(result, cacheOptions)
  80. if (!(empty(result) && cacheOptions.emptyIgnore)) {
  81. typeNode.set(key, cacheData)
  82. }
  83. return cacheData.data
  84. }
  85. /*
  86. * 是否存在缓存
  87. * @param {Object} action 缓存动作
  88. * @param {Object} payload 缓存参数
  89. * @returns {Boolean} 是否存在缓存
  90. * */
  91. const hasCache = (action, payload) => {
  92. const formatAction = checkActionDefine(action)
  93. const typeNode = container.get(formatAction.type)
  94. if (!typeNode) return false
  95. const key = generateCacheKey(payload)
  96. return typeNode.has(key)
  97. }
  98. /*
  99. * 删除缓存
  100. * @param {Object} action 缓存动作
  101. * @param {Object} payload 缓存参数
  102. * */
  103. const removeCache = (action, payload = null) => {
  104. if (payload === null || payload === undefined) {
  105. clearCache(action)
  106. return;
  107. }
  108. const formatAction = checkActionDefine(action)
  109. const typeNode = container.get(formatAction.type)
  110. if (!typeNode) return
  111. const key = generateCacheKey(payload)
  112. typeNode.delete(key)
  113. if (!typeNode.size) container.delete(formatAction.type)
  114. }
  115. /*
  116. * 清理缓存
  117. * @param {Object} action 缓存动作,action = null时清空所有缓存
  118. * */
  119. const clearCache = (action = null) => {
  120. if (!action) {
  121. container.clear()
  122. return
  123. }
  124. const formatAction = checkActionDefine(action)
  125. container.delete(formatAction.type)
  126. }
  127. /*
  128. * 清理过期缓存
  129. * */
  130. const gcCache = () => {
  131. if (!container.size) return
  132. console.log('run useCacheStore: gc')
  133. for (const [type, typeNode] of container.entries()) {
  134. for (const [key, cacheData] of typeNode.entries()) {
  135. if (cacheData.obsoleted()) {
  136. typeNode.delete(key)
  137. console.log('run useCacheStore: gc: remove cache', type, key)
  138. }
  139. }
  140. if (!typeNode.size) {
  141. container.delete(type)
  142. console.log('run useCacheStore: gc: remove type', type)
  143. }
  144. }
  145. }
  146. const getSize = function () {
  147. // then calculate
  148. let total = 0
  149. container.forEach(typeNode => {
  150. typeNode.forEach(i => {
  151. if (hasOwn(i, 'dataSize')) {
  152. total += i.dataSize
  153. } else {
  154. total += getSizeOfObject(i)
  155. }
  156. })
  157. })
  158. return total
  159. }
  160. // window.cacheContainer = container
  161. return {
  162. container, // open for test
  163. dispatchCache,
  164. hasCache,
  165. removeCache,
  166. clearCache,
  167. gcCache,
  168. getSize
  169. }
  170. })