UNPKG

cache2

Version:

一个简单的 JavaScript 缓存管理,支持浏览器端和 node 端。

671 lines (663 loc) 23.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tslib = require('tslib'); var Emitter = require('emitter-pro'); var cache = {}; var MemoryStorage = /** @class */ (function () { function MemoryStorage(scope) { if (scope === void 0) { scope = 'default'; } this.scope = scope; if (!cache[this.scope]) { cache[this.scope] = {}; } this.data = cache[this.scope]; } MemoryStorage.prototype.getItem = function (key) { return key in this.data ? this.data[key] : null; }; MemoryStorage.prototype.setItem = function (key, value) { this.data[key] = value; }; MemoryStorage.prototype.removeItem = function (key) { delete this.data[key]; }; MemoryStorage.prototype.clear = function () { cache[this.scope] = {}; this.data = cache[this.scope]; }; return MemoryStorage; }()); // 随机字符串 function randomString() { return Math.random().toString(16).substring(2, 8); } // 是否支持 storage function isStorageSupported(storage) { try { var isSupport = typeof storage === 'object' && storage !== null && !!storage.setItem && !!storage.getItem && !!storage.removeItem; if (isSupport) { var key = randomString() + new Date().getTime(); var value = '1'; storage.setItem(key, value); if (storage.getItem(key) !== value) { return false; } storage.removeItem(key); } return isSupport; } catch (e) { console.error("[cache2] ".concat(storage, " is not supported. The default memory cache will be used.")); return false; } } function parse(value, reviver) { try { return JSON.parse(value, reviver); } catch (e) { return value; } } function stringify(value, replacer) { return JSON.stringify(value, replacer); } /** * 数据存储管理。 * * @class * @param {Object} [storage] 自定义缓存对象要包含 `getItem` `setItem` `removeItem` 方法。默认使用内存缓存。 * @param {Object} [options] 配置项。可选。 * @param {boolean} [options.needParsed] 存取数据时是否需要序列化和解析数据。如果使用内置的内存缓存,默认 `false`,如果自定义 `storage` 默认 `true`。 * @param {Function} [options.replacer] 数据存储时序列化的参数,透传给 [JSON.stringify](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) 的 `replacer` 参数。仅在 `needParsed=true` 时生效。 * @param {Function} [options.reviver] 数据获取时转换的参数,透传给 [JSON.parse](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) 的 `reviver` 参数。仅在 `needParsed=true` 时生效。 * @param {string} [options.prefix] 缓存键前缀。便于管理同域名下的不同项目缓存。 * @example * // 使用内存缓存 * const memory = new Storage(); * memory.set('foo', { baz: 42 }); * memory.get('foo'); * // { baz: 42 } * * // 自定义缓存 sessionStorage 。 * const session = new Storage(window.sessionStorage); * session.set('foo', { a: 1, b: ['bar'], c: ['x', 2, 3] }); * session.get('foo'); * // { a: 1, b: ['bar'], c: ['x', 2, 3] } * * session.del('foo'); // 删除缓存 * session.get('foo'); * // null * * // 使用缓存键前缀。 * // 如果要使用内存缓存, storage 传 `undefined`。 * const local = new Storage(window.localStorage, { prefix: 'project_name' }); * local.set('foo', { baz: 42 }); * local.get('foo'); * // { baz: 42 } */ var Storage = /** @class */ (function () { function Storage(storage, options) { if (options === void 0) { options = {}; } var isSupported = storage ? isStorageSupported(storage) : false; this.options = tslib.__assign({ needParsed: isSupported, prefix: '' }, options); this.storage = isSupported ? storage : new MemoryStorage(this.options.memoryScope); } /** * 内部用于获取存储的键名称。 * * 如果实例有设置 `prefix`,返回 `prefix + key`。 * * @protected * @param key 原键名称 * @returns 存储的键名称 */ Storage.prototype.getKey = function (key) { return this.options.prefix + key; }; /** * 获取存储的数据。 * * @param {string} key 键名称。 * @returns 如果键值存在返回键值,否则返回 `null`。 * @example * const local = new Storage(window.localStorage); * local.set('foo', { baz: 42 }); * local.get('foo'); * // { baz: 42 } */ Storage.prototype.get = function (key) { var value = this.storage.getItem(this.getKey(key)); return this.options.needParsed ? parse(value, this.options.reviver) : value; }; /** * 存储数据。 * * @param key 键名称。 * @param value 键值。 * @example * const local = new Storage(window.localStorage); * local.set('foo', { baz: 42 }); * local.get('foo'); * // { baz: 42 } */ Storage.prototype.set = function (key, value) { this.storage.setItem(this.getKey(key), this.options.needParsed ? stringify(value, this.options.replacer) : value); }; /** * 删除存储的数据。 * * @param key 键名称。 * @example * const local = new Storage(window.localStorage); * local.set('foo', { baz: 42 }); * local.get('foo'); * // { baz: 42 } * * local.del('foo'); * local.get('foo'); * // null */ Storage.prototype.del = function (key) { this.storage.removeItem(this.getKey(key)); }; /** * 清除存储的所有键。 * * 注意:该方法调用 `storage.clear()`,可能会将同域下的不同实例的所有键都清除。如果要避免这种情况,建议使用 `import { Cache } 'cache2'`。 * * @example * const local = new Storage(window.localStorage); * local.set('foo', { baz: 42 }); * local.get('foo'); * // { baz: 42 } * * local.clear(); * local.get('foo'); * // null */ Storage.prototype.clear = function () { if (typeof this.storage.clear === 'function') { this.storage.clear(); } }; return Storage; }()); // 命名空间缓存键前缀。 var defaultPrefix = 'cache2_'; var defaultNamespace = 'default'; /** * 功能丰富的数据存储管理,支持 `自定义缓存` `命名空间` `数据过期时间` `限制缓存数量` `自定义事件`。 * * 注意:如果你需要的是简单的基本数据存储管理,例如浏览器存储,建议使用 `import { Storage } from 'cache2'`。 * * @class * @param {string} [namespace] 命名空间。可选。 * @param {Object} [options] 配置项。可选。 * @param {Object} [options.storage] 自定义缓存对象要包含 `getItem` `setItem` `removeItem` 方法。默认使用内置的内存缓存。 * @param {number} [options.max=-1] 最大缓存数据数量。`-1` 表示无限制。默认 `-1`。 * @param {'limited' | 'replaced'} [options.maxStrategy='limited'] 当达到最大缓存数量限制时的缓存策略。`limited` 表示达到限制数量后不存入数据,保存时返回 `false`。`replaced` 表示优先替换快过期的数据,如果都是一样的过期时间(0),按照先入先出规则处理,保存时始终返回 `true`。默认 `limited`。 * @param {number} [options.stdTTL=0] 相对当前时间的数据存活时间,应用于当前实例的所有缓存数据。单位为毫秒,`0` 表示无期限。默认 `0`。 * @param {number} [options.checkperiod=0] 定时检查过期数据,单位毫秒。如果小于等于 `0` 表示不启动定时器检查。默认 `0`。 * @param {boolean} [options.needParsed] 存取数据时是否需要序列化和解析数据。如果使用内置的内存缓存,默认 `false`,如果自定义 `storage` 默认 `true`。 * @param {Function} [options.replacer] 数据存储时序列化的参数,透传给 [JSON.stringify](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) 的 `replacer` 参数。仅在 `needParsed=true` 时生效。 * @param {Function} [options.reviver] 数据获取时转换的参数,透传给 [JSON.parse](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) 的 `reviver` 参数。仅在 `needParsed=true` 时生效。 * @param {string} [options.prefix] 缓存键前缀。 * @example * // 自定义过期时间 * const memoryCache = new Cache({ stdTTL: 60 * 1000 }); * memoryCache.set('foo', { baz: 42 }); * memoryCache.get('foo'); * // { baz: 42 } * * // 60 seconds later * * memoryCache.get('foo'); * // undefined * * // 命名空间、自定义缓存 * const localCache = new Cache('namespace', { storage: window.localStorage }); * localCache.set('foo', { baz: 42 }); * localCache.get('foo'); * // { baz: 42 } * * localCache.del('foo'); * localCache.get('foo'); * // undefined */ var Cache = /** @class */ (function (_super) { tslib.__extends(Cache, _super); function Cache(namespace, options) { var _this = _super.call(this) || this; var ns = defaultNamespace, opts; if (typeof namespace === 'string') { ns = namespace || defaultNamespace; } else if (typeof namespace === 'object') { opts = namespace; } if (!opts && typeof options === 'object') { opts = options; } _this.options = tslib.__assign({ max: -1, stdTTL: 0, maxStrategy: 'limited', checkperiod: 0, prefix: defaultPrefix }, opts); _this.storage = new Storage(_this.options.storage, tslib.__assign({ memoryScope: ns }, _this.options)); _this.cacheKey = ns; _this.startCheckperiod(); return _this; } /** * 检查当前键值是否过期,如果过期将会自动删除。 * * @param key 键名称。 * @param data 缓存数据。 * @returns 如果键值已过期返回 `false` ,否则返回 `true`。 */ Cache.prototype._check = function (key, data) { var ret = true; if (data.t !== 0 && data.t < Date.now()) { ret = false; this.del(key); this.emit('expired', key, data.v); } return ret; }; Cache.prototype._wrap = function (value, ttl) { var now = Date.now(); var currentTtl = typeof ttl === 'number' ? ttl : this.options.stdTTL; var livetime = currentTtl > 0 ? now + currentTtl : 0; return { v: value, t: livetime, n: now }; }; Cache.prototype._isLimited = function (len) { return this.options.max > -1 && len >= this.options.max; }; Cache.prototype._getReplaceKey = function (keys, cacheValues) { var retkey = keys[0]; keys.forEach(function (key) { if (cacheValues[key].t < cacheValues[retkey].t || (cacheValues[key].t === cacheValues[retkey].t && cacheValues[key].n < cacheValues[retkey].n)) { retkey = key; } }); return retkey; }; Object.defineProperty(Cache.prototype, "cacheValues", { /** * 获取全部缓存数据,不处理过期数据和排序 */ get: function () { return this.storage.get(this.cacheKey) || {}; }, enumerable: false, configurable: true }); // 设置缓存数据 Cache.prototype.setCacheValues = function (values) { this.storage.set(this.cacheKey, values); }; /** * 获取缓存值。 * * @param {string} key 键名称。 * @returns {*} 如果找到该值,则返回该值。如果未找到或已过期,则返回 `undefined`。 * @example * myCache.set('myKey', obj, 5 * 60 * 1000); * myCache.get('myKey'); * // { foo: 'bar', baz: 42 } * * myCache.get('myKey2'); * // undefined */ Cache.prototype.get = function (key) { var data = this.cacheValues[key]; if (data && this._check(key, data)) { return data.v; } return; }; /** * 获取多个缓存值。 * * @param {string[]} keys 多个键名称。 * @returns {Object} 如果找到对应键名的值,返回一个具有键值对的对象。如果未找到或已过期,则返回一个空对象 `{}`。 * @example * myCache.mset([ * { key: 'myKey', value: { foo: 'bar', baz: 42 }, ttl: 5 * 60 * 1000 }, * { key: 'myKey2', value: { a: 1, b: 2 } }, * { key: 'myKey3', value: 'abc' } * ]); * * myCache.mget(['myKey', 'myKey2']); * // { * // myKey: { foo: 'bar', baz: 42 }, * // myKey2: { a: 1, b: 2 } * // } */ Cache.prototype.mget = function (keys) { var _this = this; var ret = {}; if (!Array.isArray(keys)) { return ret; } var cacheValues = this.cacheValues; keys.forEach(function (key) { var data = cacheValues[key]; if (data && _this._check(key, data)) { ret[key] = data.v; } }); return ret; }; /** * 获取全部缓存值。 * * @returns {Object} 返回一个具有键值对的对象。 * @example * myCache.mset([ * { key: 'myKey', value: { foo: 'bar', baz: 42 }, ttl: 5 * 60 * 1000 }, * { key: 'myKey2', value: { a: 1, b: 2 } }, * { key: 'myKey3', value: 'abc' } * ]); * * myCache.getAll(); * // { * // myKey: { foo: 'bar', baz: 42 }, * // myKey2: { a: 1, b: 2 } * // myKey3: 'abc' * // } */ Cache.prototype.getAll = function () { var keys = Object.keys(this.cacheValues); return this.mget(keys); }; /** * 设置缓存数据。 * * 如果超出缓存数量,可能会设置失败。 * * @param {string} key 键名称。 * @param {*} value 键值。 * @param {number} [ttl] 数据存活时间。单位毫秒 `ms`。 * @returns {boolean} 如果设置成功返回 `true`,否则返回 `false`。 * @example * myCache.set('myKey', { foo: 'bar', baz: 42 }, 5 * 60 * 1000); * // true */ Cache.prototype.set = function (key, value, ttl) { if (this.options.max === 0) { return false; } var cacheValues = this.cacheValues; var keys = Object.keys(cacheValues); // 当前不存在该键值,并且数据量超过最大限制 if (!cacheValues[key] && this._isLimited(keys.length)) { var validKeys = this.keys(); if (this._isLimited(validKeys.length)) { // 如果最大限制策略是替换,将优先替换快过期的数据,如果都是一样的过期时间(0),按照先入先出规则处理。 if (this.options.maxStrategy === 'replaced') { var replaceKey = this._getReplaceKey(validKeys, cacheValues); this.del(replaceKey); } else { // 如果是最大限制策略是不允许添加,返回 false 。 return false; } } } cacheValues[key] = this._wrap(value, ttl); this.setCacheValues(cacheValues); this.emit('set', key, cacheValues[key].v); return true; }; /** * 设置多个缓存数据。 * * @param {Object[]} keyValueSet 多个键值对数据。 * @returns {boolean} 如果全部设置成功返回 `true`,否则返回 `false`。 * @example * myCache.mset([ * { key: 'myKey', value: { foo: 'bar', baz: 42 }, ttl: 5 * 60 * 1000 }, * { key: 'myKey2', value: { a: 1, b: 2 } }, * { key: 'myKey3', value: 'abc' } * ]); * // true */ Cache.prototype.mset = function (keyValueSet) { var _this = this; // 该处不使用数组 some 方法,是因为不能某个失败,而导致其他就不在更新。 var ret = true; keyValueSet.forEach(function (item) { var itemSetResult = _this.set(item.key, item.value, item.ttl); if (ret && !itemSetResult) { ret = false; } }); return ret; }; /** * 删除一个或多个键。 * * @param {string|string[]} key 要删除的键名。 * @returns {number} 返回已删除的数量。 * @example * myCache.set('myKey', { foo: 'bar', baz: 42 }); * myCache.del('myKey'); // 1 * myCache.del('not found'); // 0 * * myCache.mset([ * { key: 'myKey', value: { foo: 'bar', baz: 42 }, ttl: 5 * 60 * 1000 }, * { key: 'myKey2', value: { a: 1, b: 2 } }, * { key: 'myKey3', value: 'abc' } * ]); * myCache.del(['myKey', 'myKey2']); // 2 */ Cache.prototype.del = function (key) { var _this = this; var cacheValues = this.cacheValues; var count = 0; var keys = Array.isArray(key) ? key : [key]; keys.forEach(function (key) { if (cacheValues[key]) { count++; var oldData = cacheValues[key]; delete cacheValues[key]; _this.emit('del', key, oldData.v); } }); if (count > 0) { this.setCacheValues(cacheValues); } return count; }; /** * 清除全部缓存的数据。 * * @example * myCache.set('bar', 1); * myCache.set('foo', 2); * myCache.keys(); // ['bar', 'foo'] * * myCache.clear(); * myCache.keys(); // [] */ Cache.prototype.clear = function () { this.storage.del(this.cacheKey); }; /** * 获取全部键名的数组。 * * @returns {string[]} 返回全部键名的数组。 * @example * myCache.set('bar', 1); * myCache.set('foo', 2); * * myCache.keys(); // ['bar', 'foo'] */ Cache.prototype.keys = function () { var _this = this; var cacheValues = this.cacheValues; var keys = Object.keys(cacheValues); return keys.filter(function (key) { return _this._check(key, cacheValues[key]); }); }; /** * 判断是否存在某个键。 * * @param {string} key 键名称。 * @returns {boolean} 如果包含该键返回 `true`,否则返回 `false`。 * @example * myCache.has('foo'); // false * * myCache.set('foo', 1); * myCache.has('foo'); // true */ Cache.prototype.has = function (key) { var data = this.cacheValues[key]; return !!(data && this._check(key, data)); }; /** * 获取缓存值并从缓存中删除键。 * * @param {string} key 键名称。 * @returns {*} 如果找到该值,则返回该值,并从缓存中删除该键。如果未找到或已过期,则返回 `undefined`。 * @example * myCache.set('myKey', 'myValue'); * myCache.has('myKey'); // true * * myCache.take('myKey'); // 'myValue' * myCache.has('myKey'); // false */ Cache.prototype.take = function (key) { var ret; var data = this.cacheValues[key]; if (data && this._check(key, data)) { ret = data.v; this.del(key); } return ret; }; /** * 更新缓存键值的数据存活时间。 * * @param {string} key 键名称。 * @param {number} ttl 数据存活时间。 * @returns {boolean} 如果找到并更新成功,则返回 `true`,否则返回 `false`。 * @example * myCache.set('myKey', { foo: 'bar', baz: 42 }, 5 * 60 * 1000); * myCache.ttl('myKey', 60 * 1000); * // true * * myCache.ttl('not found', 1000); * // false */ Cache.prototype.ttl = function (key, ttl) { var cacheValues = this.cacheValues; var data = cacheValues[key]; if (data && this._check(key, data)) { cacheValues[key] = this._wrap(data.v, ttl); return true; } return false; }; /** * 获取某个键的过期时间戳。 * * @param {string} key 键名称。 * @returns {number | undefined} 如果未找到键或已过期,返回 `undefined`。如果 `ttl` 为 `0`,返回 `0`,否则返回一个以毫秒为单位的时间戳,表示键值将过期的时间。 * @example * const myCache = new Cache({ stdTTL: 5 * 1000 }); * * // 假如 Date.now() = 1673330000000 * myCache.set('ttlKey', 'expireData'); * myCache.set('noTtlKey', 'nonExpireData', 0); * * myCache.getTtl('ttlKey'); // 1673330005000 * myCache.getTtl('noTtlKey'); // 0 * myCache.getTtl('unknownKey'); // undefined */ Cache.prototype.getTtl = function (key) { var cacheValues = this.cacheValues; var data = cacheValues[key]; if (data && this._check(key, data)) { return cacheValues[key].t; } return; }; /** * 获取某个键值的最后修改时间。 * * @param {string} key 键名称。 * @returns {number | undefined} 如果未找到键或已过期,返回 `undefined`,否则返回一个以毫秒时间戳,表示键值最后修改时间。 * @example * const myCache = new Cache(); * * // 假如 Date.now() = 1673330000000 * myCache.set('myKey', 'foo'); * myCache.getLastModified('myKey'); // 1673330000000 * * // 5000ms later * myCache.set('myKey', 'bar'); * myCache.getLastModified('myKey'); // 1673330005000 */ Cache.prototype.getLastModified = function (key) { var cacheValues = this.cacheValues; var data = cacheValues[key]; if (data && this._check(key, data)) { return cacheValues[key].n; } return; }; /** * 启动定时校验过期数据。 * * 注意,如果没有设置 `checkperiod` 将不会触发定时器。 * * @example * // 设置 checkperiod 之后自动生效 * const myCache = new Cache({ * checkperiod: 10 * 60 * 1000 // 10分钟检查一次数据是否过期 * }); * * // 停止定时校验过期数据 * myCache.stopCheckperiod(); * * // 启动定时校验过期数据 * myCache.startCheckperiod(); */ Cache.prototype.startCheckperiod = function () { var _this = this; // 触发全部缓存数据是否过期校验 this.keys(); if (this.options.checkperiod > 0) { clearTimeout(this._checkTimeout); this._checkTimeout = setTimeout(function () { _this.startCheckperiod(); }, this.options.checkperiod); } }; /** * 停止定时校验过期数据。 * * @example * // 设置 checkperiod 之后自动生效 * const myCache = new Cache({ * checkperiod: 10 * 60 * 1000 // 10分钟检查一次数据是否过期 * }); * * // 停止定时校验过期数据 * myCache.stopCheckperiod(); */ Cache.prototype.stopCheckperiod = function () { clearTimeout(this._checkTimeout); }; return Cache; }(Emitter)); exports.Cache = Cache; exports.Storage = Storage; exports.default = Cache;