AsyncMemo.js

  1. import { Cache } from 'cache2';
  2. import { isString } from 'ut2';
  3. /**
  4. * 异步缓存
  5. *
  6. * 特点:
  7. * 1. 共享异步。同一个缓存键的异步,最多同时运行一个异步,异步结果共享。
  8. * 2. 持久化数据。存在缓存结果,不再执行异步方法,直接返回该结果。
  9. * 3. 每个实例都有独立的缓存空间。相互之间隔离,缓存灵活配置,更多配置请查阅 [`cache2`](https://www.npmjs.com/package/cache2)。
  10. *
  11. * @class
  12. * @see {@link https://www.npmjs.com/package/cache2 cache2}
  13. * @param {Object} [options] 缓存配置项,更多配置项可参考 [`cache2`](https://www.npmjs.com/package/cache2)。
  14. * @param {number} [options.max] 最大缓存数量。
  15. * @param {'replaced' | 'limited'} [options.maxStrategy] 缓存策略。
  16. * @param {string} [options.prefix] 缓存健前缀。
  17. * @param {string} [ns='uh_async_memo'] 缓存命名空间。默认 `uh_async_memo`。
  18. * @example
  19. *
  20. * const asyncMemo = new AsyncMemo({ max: 20, maxStrategy: 'replaced', prefix: 'some key' });
  21. * asyncMemo.run(()=>download({ fssid: 'a' }), 'a');
  22. * asyncMemo.run(()=>download({ fssid: 'b' }), 'b');
  23. * asyncMemo.run(()=>download({ fssid: 'a' }), 'a'); // 如果有缓存结果直接返回,如果有异步执行中,不会重复触发异步,但共享异步结果。
  24. *
  25. * asyncMemo.run(()=>download({ fssid: 'a' }), 'a', { persisted: false }); // 不读取缓存结果,但是异步执行结果还是会缓存。
  26. * asyncMemo.run(()=>download({ fssid: 'a' })); // 没有缓存键时,直接执行异步方法,不读取缓存结果,也不会缓存异步结果。
  27. *
  28. * // 自定义命名空间
  29. * const asyncMemo2 = new AsyncMemo({}, 'namespace');
  30. *
  31. */
  32. class AsyncMemo {
  33. promiseCache;
  34. /**
  35. * @summary cache2 实例,用于管理缓存
  36. */
  37. cache;
  38. constructor(options, ns = 'uh_async_memo') {
  39. this.promiseCache = {};
  40. this.cache = new Cache(ns, options);
  41. }
  42. /**
  43. * 执行异步方法
  44. *
  45. * @param {Function} asyncFn 异步方法
  46. * @param {string} [key] 缓存键,如果没有该值将直接执行异步方法。
  47. * @param {Object} [options] 配置项
  48. * @param {number} [options.ttl] 数据存活时间
  49. * @param {boolean} [options.persisted=true] 数据持久化,默认`true`。如果存在缓存数据,直接返回缓存数据,不再执行异步方法。<br/>即使不开启该配置,不影响在异步执行成功后缓存数据。
  50. * @returns {Promise<*>} 异步结果
  51. * @example
  52. *
  53. * const asyncMemo = new AsyncMemo();
  54. * asyncMemo.run(()=>download({ fssid: 'a' }), 'a');
  55. * asyncMemo.run(()=>download({ fssid: 'b' }), 'b');
  56. * asyncMemo.run(()=>download({ fssid: 'a' }), 'a'); // 如果有缓存结果直接返回,如果有异步执行中,不会重复触发异步,但共享异步结果。
  57. *
  58. * asyncMemo.run(()=>download({ fssid: 'a' }), 'a', { persisted: false }); // 不读取缓存结果,但是异步执行结果还是会缓存。
  59. * asyncMemo.run(()=>download({ fssid: 'a' })); // 没有缓存键时,直接执行异步方法,不读取缓存结果,也不会缓存异步结果。
  60. */
  61. run(asyncFn, key, options) {
  62. if (!key || !isString(key)) {
  63. return asyncFn();
  64. }
  65. const opts = {
  66. persisted: true,
  67. ...options
  68. };
  69. if (opts.persisted) {
  70. const data = this.cache.get(key);
  71. if (data) {
  72. return Promise.resolve(data);
  73. }
  74. }
  75. if (!this.promiseCache[key]) {
  76. this.promiseCache[key] = asyncFn()
  77. .then((res) => {
  78. delete this.promiseCache[key];
  79. this.cache.set(key, res, opts.ttl);
  80. return res;
  81. })
  82. .catch((err) => {
  83. delete this.promiseCache[key];
  84. return Promise.reject(err);
  85. });
  86. }
  87. return this.promiseCache[key];
  88. }
  89. }
  90. export default AsyncMemo;