高性能JavaScript LRU缓存库,可设置最大条目数、大小限制及TTL,支持过期处理、异步获取等功能,适用于多种缓存场景。【此简介由AI生成】
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 2 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 3 年前 | ||
| 3 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 2 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 | ||
| 1 年前 |
LRU 缓存
一个自动删除最近最少使用项的缓存对象。
您可以指定希望保留的最近使用项的最大数量,该缓存将动态维护这些最新访问的条目。
请注意,这并非专门的 TTL(生存时间)缓存,也不提供严格的 TTL 保证。默认情况下,系统不会主动清理过期项,但您可以为整个缓存或单个 set 操作设置 TTL。若启用此功能,缓存会将过期项视为缺失数据,并在获取时自动删除它们。如果您更关注 TTL 缓存而非 LRU 机制,推荐使用 @isaacs/ttlcache。
自第 7 版起,这是 JavaScript 中性能最优异的 LRU 实现之一,能适应多样化的使用场景。但需注意,启用某些特性将不可避免地影响性能,因为缓存需要执行更多操作。详见下文"性能"章节。
安装说明
npm install lru-cache --save
使用方法
// hybrid module, either works
import { LRUCache } from 'lru-cache'
// or:
const { LRUCache } = require('lru-cache')
// or in minified form for web browsers:
import { LRUCache } from 'http://unpkg.com/lru-cache@9/dist/mjs/index.min.mjs'
// At least one of 'max', 'ttl', or 'maxSize' is required, to prevent
// unsafe unbounded storage.
//
// In most cases, it's best to specify a max for performance, so all
// the required memory allocation is done up-front.
//
// All the other options are optional, see the sections below for
// documentation on what each one does. Most of them can be
// overridden for specific items in get()/set()
const options = {
max: 500,
// for use with tracking overall storage size
maxSize: 5000,
sizeCalculation: (value, key) => {
return 1
},
// for use when you need to clean up something when objects
// are evicted from the cache
dispose: (value, key) => {
freeFromMemoryOrWhatever(value)
},
// how long to live in ms
ttl: 1000 * 60 * 5,
// return stale items before removing from cache?
allowStale: false,
updateAgeOnGet: false,
updateAgeOnHas: false,
// async method to use for cache.fetch(), for
// stale-while-revalidate type of behavior
fetchMethod: async (
key,
staleValue,
{ options, signal, context }
) => {},
}
const cache = new LRUCache(options)
cache.set('key', 'value')
cache.get('key') // "value"
// non-string keys ARE fully supported
// but note that it must be THE SAME object, not
// just a JSON-equivalent object.
var someObject = { a: 1 }
cache.set(someObject, 'a value')
// Object keys are not toString()-ed
cache.set('[object Object]', 'a different value')
assert.equal(cache.get(someObject), 'a value')
// A similar object with same keys/values won't work,
// because it's a different object identity
assert.equal(cache.get({ a: 1 }), undefined)
cache.clear() // empty the cache
如果你往缓存里塞更多东西,最近最少使用的条目就会被挤出去。这就是 LRU 缓存的核心机制。
class LRUCache<K, V, FC = unknown>(options)
创建一个新的 LRUCache 对象。
使用 TypeScript 时,请将 K 和 V 类型分别设为键和值的类型。
FC("fetch context" 获取上下文)泛型类型默认为 unknown。如果设置为非 void 或 undefined 的值,那么任何调用 cache.fetch() 时 必须 提供一个与 FC 类型匹配的 context 选项。如果 FC 设为 void 或 undefined,则 cache.fetch() 不得 提供 context 选项。详见下文 async fetch() 的文档。
选项
所有选项都可在 LRUCache 实例上访问,因此可以安全地将一个 LRUCache 实例作为选项参数传入,以创建另一个同类型的空缓存。
部分选项标记为只读,因为实例化后修改它们是不安全的。更改其他选项当然只会影响后续的方法调用。
max(只读)
缓存中保留的最大条目数(假设没有 TTL 清理或显式删除)。注意,如果启用了大小计算且 maxSize 被超过,实际存储的条目可能更少。必须是一个正有限整数。
至少需要设置 max、maxSize 或 TTL 中的一个。如果设置,必须为正整数。
强烈建议设置 max 以防止缓存无限增长。 见下文“存储边界安全”。
maxSize(只读)
设置为正整数以跟踪添加到缓存的条目大小,并自动淘汰条目以保持在此大小以下。注意,这可能导致存储的条目少于 max。
尝试添加一个计算大小超过此值的条目将无效。该条目不会被缓存,也不会淘汰其他条目。
可选,如果提供则必须为正整数。
除非为 maxEntrySize 提供了不同的值,否则会将 maxEntrySize 设为相同值。
至少需要设置 max、maxSize 或 TTL 中的一个。如果设置,必须为正整数。
即使启用了大小跟踪,强烈建议设置 max 以防止缓存无限增长。 见下文“存储边界安全”。
maxEntrySize
设置为正整数以跟踪添加到缓存的条目大小,并防止缓存超过指定大小的条目。尝试添加一个计算大小超过此值的条目将无效。该条目不会被缓存,也不会淘汰其他条目。
可选,如果提供则必须为正整数。默认为 maxSize 的值(如果提供)。
sizeCalculation
用于计算存储条目大小的函数。如果你存储字符串或缓冲区,可能需要类似 n => n.length 的函数。条目作为第一个参数传入,键作为第二个参数传入。
可通过向 cache.set() 传递选项对象来覆盖此设置。
需要设置 maxSize。
如果某个条目的 size(或 sizeCalculation 的返回值)大于 maxEntrySize,则该条目不会被添加到缓存。
fetchMethod(只读)
用于后台异步获取的函数。调用形式为 fetchMethod(key, staleValue, { signal, options, context })。可以返回一个 Promise。
如果未提供 fetchMethod,则 cache.fetch(key) 等同于 Promise.resolve(cache.get(key))。
如果在任何时候 signal.aborted 被设为 true,或调用了 signal.onabort 方法,或触发了 'abort' 事件(可通过 addEventListener 监听),则表示应放弃获取。这可以传递给支持 AbortController/AbortSignal 行为的异步函数。
fetchMethod 仅在 AbortController 触发 abort 事件时应返回 undefined 或解析为 undefined 的 Promise。其他所有情况下,它应返回或解析为适合添加到缓存的值。
options 对象是 set() 和 get() 可能提供的选项的联合。如果修改这些选项,将在值解析时修改 cache.set() 的设置,而对于 noDeleteOnFetchRejection 和 allowStaleOnFetchRejection,会影响 fetchMethod 失败的处理。
例如,DNS 缓存可能根据远程 DNS 服务器返回的值,通过在 fetchMethod 中修改 options.ttl 来更新 TTL。
noDeleteOnFetchRejection
如果 fetchMethod 抛出错误或返回拒绝的 Promise,默认情况下会从缓存中移除任何现有的陈旧值。
如果 noDeleteOnFetchRejection 设为 true,则此行为被抑制,fetchMethod 拒绝时陈旧值仍保留在缓存中。
这在 fetchMethod 仅作为后台更新调用而返回陈旧值(使用 allowStale 时)的情况下非常重要。
当 allowStaleOnFetchRejection 设置时,此选项隐式生效。
可以在 fetch() 调用中设置,或在构造函数中默认,或通过修改 fetchMethod 中的选项对象来覆盖。
allowStaleOnFetchRejection
设为 true 时,当 fetchMethod 抛出错误或返回拒绝的 Promise 时,从缓存返回陈旧值。
如果 fetchMethod 失败且没有可用的陈旧值,fetch() 将解析为 undefined。即所有 fetchMethod 错误被抑制。
隐含 noDeleteOnFetchRejection。
可以在 fetch() 调用中设置,或在构造函数中默认,或通过修改 fetchMethod 中的选项对象来覆盖。
allowStaleOnFetchAbort
设为 true 时,当传递给 fetchMethod 的 AbortSignal 触发 'abort' 事件(无论是用户触发还是内部缓存行为导致)时,从缓存返回陈旧值。
除非同时设置 ignoreFetchAbort,否则底层的 fetchMethod 仍被视为已取消,其返回的任何值将被忽略且不缓存。
注意:由于在缓存中显式设置新值时会中止获取,这可能导致 fetch 返回陈旧值,因为那是 fetch() 启动时刻 的回退值,即使新更新的值现在已在缓存中。
例如:
const cache = new LRUCache<string, any>({
ttl: 100,
fetchMethod: async (url, oldValue, { signal }) => {
const res = await fetch(url, { signal })
return await res.json()
}
})
cache.set('https://example.com/', { some: 'data' })
// 100ms go by...
const result = cache.fetch('https://example.com/')
cache.set('https://example.com/', { other: 'thing' })
console.log(await result) // { some: 'data' }
console.log(cache.get('https://example.com/')) // { other: 'thing' }
ignoreFetchAbort
设置为 true 时,将忽略传递给 fetchMethod 的 AbortSignal 对象发出的 abort 事件,并且只要结果解析值不是 undefined,仍会缓存该值。
单独使用时,这意味着被中止的 fetch() 调用不会在终止时立即解析或拒绝,而是会等待完整的时间。
与 allowStaleOnFetchAbort 结合使用时,被中止的 fetch() 调用会立即解析为过期的缓存值或 undefined,并且只要结果值不是 undefined,它们会在解析后继续处理并最终更新缓存,从而支持通过传递 AbortSignal.timeout(n) 作为信号来实现“超时返回过期数据同时刷新”的机制。
例如:
const c = new LRUCache({
ttl: 100,
ignoreFetchAbort: true,
allowStaleOnFetchAbort: true,
fetchMethod: async (key, oldValue, { signal }) => {
// note: do NOT pass the signal to fetch()!
// let's say this fetch can take a long time.
const res = await fetch(`https://slow-backend-server/${key}`)
return await res.json()
},
})
// this will return the stale value after 100ms, while still
// updating in the background for next time.
const val = await c.fetch('key', { signal: AbortSignal.timeout(100) })
注意:无论此设置如何,abort事件仍会在AbortSignal对象上触发,因此在传递给其他使用AbortSignals的底层API时可能导致无效结果。
此设置可在fetch()调用或fetchMethod本身中被覆盖。
dispose(只读)
当项从缓存中移除时调用的函数,形式为this.dispose(value, key, reason)。
如果你想在项不再存储在缓存中时关闭文件描述符或执行其他清理任务,这会非常有用。
注意:它是在项完全从缓存中移除_之前_调用的,因此如果你想立即将其重新放入缓存,需要等到下一个时间片。如果在dispose()函数调用期间尝试将其重新添加,会以微妙且奇怪的方式破坏功能。
与其他几个选项不同,出于性能考虑,此选项_不能_通过向set()传递选项来覆盖。
reason会是以下字符串之一,对应项被删除的原因:
evict项被驱逐以腾出新空间set项被新值覆盖delete项通过显式cache.delete(key)或调用cache.clear()(删除所有内容)移除
dispose()方法_不会_为取消的fetchMethod()调用而调用。如果你想处理正在进行的异步获取的驱逐、覆盖和删除,必须使用提供的AbortSignal。
可选,必须是一个函数。
disposeAfter(只读)
与dispose相同,但在项完全移除且缓存再次处于干净状态后调用。
此时可以安全地将项重新添加到缓存中。但请注意,这样很容易无意中创建无限递归。
disposeAfter()方法_不会_为取消的fetchMethod()调用而调用。如果你想处理正在进行的异步获取的驱逐、覆盖和删除,必须使用提供的AbortSignal。
noDisposeOnSet
设置为true以在项键仍可在缓存中访问时禁止调用dispose()函数。
可以通过向cache.set()传递选项对象来覆盖此设置。
布尔值,默认为false。仅在设置了dispose或disposeAfter选项时相关。
ttl
项在被视为陈旧前的最大存活时间。请注意,默认情况下陈旧项不会被主动移除,它们可能会长期存在于缓存中,影响其LRU最大值。
此外,由于此缓存针对LRU/MRU操作进行了优化,某些陈旧性/TTL检查会降低性能。
这主要不是一个TTL缓存,也不提供严格的TTL保证。没有对过期项的主动清理,但你可以_在缓存上设置TTL,当获取过期项时将其视为缺失并删除。
可选,但如果指定必须是一个正整数(毫秒)。
可以通过向cache.set()传递选项对象来覆盖此设置。
至少需要max、maxSize或TTL中的一个。如果设置,必须是一个正整数。
即使启用了TTL跟踪,强烈建议设置max以防止缓存无限增长。 参见下面的“存储边界安全”。
如果启用了TTL跟踪,且未设置max和maxSize,且未设置ttlAutopurge,则会发出警告,提醒可能存在无限内存消耗。(TypeScript定义也会阻止这种情况。)
noUpdateTTL
布尔标志,告诉缓存在为现有键设置新值时不要更新TTL(即更新值而非插入新值时)。请注意,TTL值在向缓存添加新条目时_总是_设置(如果提供)。
可以作为选项传递给cache.set()。
布尔值,默认为false。
ttlResolution
检查陈旧性的最小时间间隔(毫秒)。默认为1,意味着最多每毫秒检查一次当前时间。
设置为0以在每次测试陈旧性时检查当前时间。
请注意,将此值设置为较高值会在使用TTL跟踪时提高性能,尽管代价是让陈旧项比预期保留更长时间。
ttlAutopurge
主动从缓存中移除陈旧项。
请注意,这可能会_显著_降低性能,尤其是当缓存存储大量项时。最好只是让陈旧项留在缓存中,随着新项的添加而自然淘汰。
请注意,这意味着allowStale有点多余,因为陈旧项几乎在过期后立即被删除。
谨慎使用!
布尔值,默认为false
allowStale
默认情况下,如果设置了ttl,它只会在你get(key)时从缓存中删除陈旧项。也就是说,它不会主动清理项。
如果设置allowStale:true,它会返回陈旧值并删除它。如果不设置此选项,则尝试获取陈旧项时会返回undefined。
请注意,当获取陈旧项时,即使由于设置了allowStale而返回,它也会立即从缓存中移除。你可以立即将其重新放入缓存,从而重置TTL。
可以通过向cache.get()传递选项对象来覆盖此设置。cache.has()方法对于陈旧项总是返回false。
布尔值,默认为false,仅在设置了ttl时相关。
noDeleteOnStaleGet
当使用带ttl的时间过期项时,默认情况下,当通过cache.get()访问键时,陈旧项会从缓存中移除。
将noDeleteOnStaleGet设置为true会使陈旧项保留在缓存中,直到通过cache.delete(key)显式删除,或通过noDeleteOnStaleGet设置为false获取。
可以通过向cache.get()传递选项对象来覆盖此设置。
布尔值,默认为false,仅在设置了ttl时相关。
updateAgeOnGet
当使用带ttl的时间过期项时,将此设置为true会使每次通过get()从缓存中获取项时,项的年龄重置为0,使其不会过期。(当然,它仍可能基于使用频率从缓存中淘汰。)
可以通过向cache.get()传递选项对象来覆盖此设置。
布尔值,默认为false,仅在设置了ttl时相关。
updateAgeOnHas
当使用带ttl的时间过期项时,将此设置为true会使每次通过has()检查项在缓存中的存在时,项的年龄重置为0,使其不会过期。(当然,它仍可能基于使用频率从缓存中淘汰。)
可以通过向cache.has()传递选项对象来覆盖此设置。
布尔值,默认为false,仅在设置了ttl时相关。
API
new LRUCache<K, V, FC = unknown>(options)
创建一个新的LRUCache。所有选项都在上面文档中,并作为公共成员存在于缓存上。
K和V类型分别定义键和值的类型。可选的FC类型定义传递给cache.fetch()的context对象的类型。
键和值不能为null或undefined。
cache.max, cache.maxSize, cache.allowStale,
cache.noDisposeOnSet, cache.sizeCalculation, cache.dispose,
cache.maxSize, cache.ttl, cache.updateAgeOnGet,
cache.updateAgeOnHas
所有选项名称均作为公共成员暴露在缓存对象上。
这些选项仅供读取访问。在程序运行期间修改它们可能导致未定义行为。
cache.size
当前时刻缓存中持有的总条目数。
cache.calculatedSize
当启用大小追踪时,缓存中条目的总大小。
set(key, value, [{ size, sizeCalculation, ttl, noDisposeOnSet, start, status }])
向缓存添加一个值。
可选配置对象可包含上述描述的 ttl 和 sizeCalculation,默认使用缓存对象的设置。
若提供 start,则将作为 TTL 计算的有效起始时间。注意,若支持应使用 performance.now() 的先前值,否则使用 Date.now() 的先前值。
配置对象也可包含 size,这将避免调用 sizeCalculation 函数,仅当其为正整数时直接使用指定数值;以及 noDisposeOnSet,这将避免在覆盖时调用 dispose 函数。
若某条目的 size(或 sizeCalculation 返回值)大于 maxEntrySize,则该条目不会被加入缓存。
将更新条目的最近使用时间。
返回缓存对象。
关于 status 选项的使用,参见下文状态追踪部分。
若值为 undefined,则此操作为 cache.delete(key) 的别名。undefined 永远不会被存入缓存。参见下文存储未定义值部分。
get(key, { updateAgeOnGet, allowStale, status } = {}) => value
从缓存中返回值。
将更新找到的缓存条目的最近使用时间。
若未找到键,get() 将返回 undefined。
关于 status 选项的使用,参见下文状态追踪部分。
async fetch(key, options = {}) => Promise
支持以下选项:
updateAgeOnGetallowStalesizesizeCalculationttlnoDisposeOnSetforceRefreshstatus- 参见下文状态追踪部分。signal- 可使用 AbortSignal 取消fetch()。注意提供给fetchMethod的signal选项是不同对象,因为它必须同时响应内部缓存状态变化,但中止此信号也会中止传递给fetchMethod的信号。context- 设置传递给底层fetchMethod的context选项。
若值在缓存中且未过期,则返回的 Promise 解析为该值。
若不在缓存中或超出 TTL 过期时间,则调用 fetchMethod(key, staleValue, { options, signal, context }),解析后的值将被加入缓存。
若调用时带有 allowStale,且当前正在异步获取以重新加载过期值,则将返回先前的过期值。
若调用时带有 forceRefresh,则将重新获取缓存项,即使其未过期。然而,若设置了 allowStale,仍将返回旧值。这在需要强制重新加载缓存值的场景中非常有用。若后台获取已在进行中,则 forceRefresh 无效。
对同一 key 的多次获取仅会调用 fetchMethod 一次,所有请求将在值解析时一同解析,即使使用了不同选项。
若未指定 fetchMethod,则此方法实际上是 Promise.resolve(cache.get(key)) 的别名。
当获取方法解析为一个值时,若获取未被因删除、淘汰或覆盖而中止,则将使用提供的选项将其加入缓存。
若键在 fetchMethod 解析前被淘汰或删除,则传递给 fetchMethod 的 AbortSignal 将接收 abort 事件,且 fetch() 返回的 Promise 将因中止原因而拒绝。
若向 fetch() 调用传递了 signal,则中止该信号将中止获取,并导致 fetch() 的 Promise 因提供的原因而拒绝。
设置 context
若在 LRUCache 构造函数中将 FC 类型设置为 unknown、void 或 undefined 以外的类型,则所有 cache.fetch() 调用_必须_提供 context 选项。若设置为 undefined 或 void,则 fetch 调用_不得_提供 context 选项。
context 参数允许提供在获取数据过程中可能相关的任意数据。它仅对单次 fetch() 操作相关,之后将被丢弃。
注意:fetch() 调用是唯一进行中的
若使用相同键值多次调用 fetch(),则首次调用后的每次调用都将基于同一 Promise 解析1,
即使它们有不同的设置,这些设置本会改变获取行为,如 noDeleteOnFetchRejection 或 ignoreFetchAbort。
在大多数情况下,这不是问题(事实上,若你首先进行缓存,仅获取一次可能是你想要的)。若你在不同运行间大幅改变 fetch() 选项,很可能你正尝试将不同语义塞入单一对象,此时使用多个缓存实例可能更合适。
1:即它们不是“同一 Promise”,但它们会在同一时间解析,因为它们都在等待同一底层 fetchMethod 响应。
peek(key, { allowStale } = {}) => value
类似 get() 但不更新最近使用时间或删除过期条目。
若条目过期则返回 undefined,除非在缓存或选项对象中设置了 allowStale。
has(key, { updateAgeOnHas, status } = {}) => Boolean
检查键是否在缓存中,不更新使用的新近度。若在选项或构造函数中 updateAgeOnHas 设为 true,则更新使用时间。
若条目过期,即使技术上仍在缓存中,也将返回 false。可通过使用 status 参数并检查 has 字段来确定差异(若重要)。
关于 status 选项的使用,参见下文状态追踪部分。
delete(key)
从缓存中删除键。
若键被删除则返回 true,否则返回 false。
clear()
完全清空缓存,丢弃所有值。
keys()
返回一个生成器,按从最近使用到最不常使用的顺序生成缓存中的键。
rkeys()
返回一个生成器,按从最不常使用到最近使用的顺序生成缓存中的键。
values()
返回一个生成器,按从最近使用到最不常使用的顺序生成缓存中的值。
rvalues()
返回一个生成器,按从最不常使用到最近使用的顺序生成缓存中的值。
entries()
返回一个生成器,按从最近使用到最少使用的顺序生成 [key, value] 键值对。
rentries()
返回一个生成器,按从最少使用到最近使用的顺序生成 [key, value] 键值对。
find(fn, [getOptions])
查找使提供的 fn 方法返回真值的值,类似于 Array.find()。
fn 的调用形式为 fn(value, key, cache)。
可选的 getOptions 会应用于找到的项的 get() 结果。
dump()
返回一个 [key, entry] 对象数组,这些对象可以传递给 cache.load()。
start 字段是相对于可移植的 Date.now() 时间戳计算的,即使 performance.now() 可用。
即使 allowStale 为 false,过期的条目也会包含在 dump 中。
注意:这会返回一个实际的数组,而不是生成器,因此可以更方便地传递。
load(entries)
重置缓存并按列出的顺序加载 entries 中的项。注意,如果两个缓存中使用的选项不同,生成的缓存结构可能会有所不同。
start 字段假定是相对于可移植的 Date.now() 时间戳计算的,即使 performance.now() 可用。
purgeStale()
删除所有过期的条目。如果有任何条目被移除,返回 true,否则返回 false。
getRemainingTTL(key)
返回项剩余 TTL 的毫秒数。如果项不在缓存中,返回 0。如果项在缓存中没有定义 TTL,返回 Infinity。
forEach(fn, [thisp])
对 LRU 缓存中的每组 fn(value, key, cache) 调用 fn 函数,顺序从最近使用到最少使用。
不影响使用的新近度。
如果提供了 thisp,函数将在提供的对象的 this 上下文中调用。
rforEach(fn, [thisp])
与 cache.forEach(fn, thisp) 相同,但顺序是从最少使用到最近使用。
pop()
驱逐最少使用的项,并返回其值。
如果缓存为空,返回 undefined。
状态跟踪
有时,跟踪缓存的内部行为可能很有用,特别是对于日志记录、调试或 fetchMethod 内的行为。为此,可以将 status 对象传递给 get()、set()、has() 和 fetch() 方法。
status 选项应为一个普通的 JavaScript 对象。
将适当设置以下字段:
interface Status<V> {
/**
* The status of a set() operation.
*
* - add: the item was not found in the cache, and was added
* - update: the item was in the cache, with the same value provided
* - replace: the item was in the cache, and replaced
* - miss: the item was not added to the cache for some reason
*/
set?: 'add' | 'update' | 'replace' | 'miss'
/**
* the ttl stored for the item, or undefined if ttls are not used.
*/
ttl?: LRUMilliseconds
/**
* the start time for the item, or undefined if ttls are not used.
*/
start?: LRUMilliseconds
/**
* The timestamp used for TTL calculation
*/
now?: LRUMilliseconds
/**
* the remaining ttl for the item, or undefined if ttls are not used.
*/
remainingTTL?: LRUMilliseconds
/**
* The calculated size for the item, if sizes are used.
*/
size?: LRUSize
/**
* A flag indicating that the item was not stored, due to exceeding the
* {@link maxEntrySize}
*/
maxEntrySizeExceeded?: true
/**
* The old value, specified in the case of `set:'update'` or
* `set:'replace'`
*/
oldValue?: V
/**
* The results of a {@link has} operation
*
* - hit: the item was found in the cache
* - stale: the item was found in the cache, but is stale
* - miss: the item was not found in the cache
*/
has?: 'hit' | 'stale' | 'miss'
/**
* The status of a {@link fetch} operation.
* Note that this can change as the underlying fetch() moves through
* various states.
*
* - inflight: there is another fetch() for this key which is in process
* - get: there is no fetchMethod, so {@link get} was called.
* - miss: the item is not in cache, and will be fetched.
* - hit: the item is in the cache, and was resolved immediately.
* - stale: the item is in the cache, but stale.
* - refresh: the item is in the cache, and not stale, but
* {@link forceRefresh} was specified.
*/
fetch?: 'get' | 'inflight' | 'miss' | 'hit' | 'stale' | 'refresh'
/**
* The {@link fetchMethod} was called
*/
fetchDispatched?: true
/**
* The cached value was updated after a successful call to fetchMethod
*/
fetchUpdated?: true
/**
* The reason for a fetch() rejection. Either the error raised by the
* {@link fetchMethod}, or the reason for an AbortSignal.
*/
fetchError?: Error
/**
* The fetch received an abort signal
*/
fetchAborted?: true
/**
* The abort signal received was ignored, and the fetch was allowed to
* continue.
*/
fetchAbortIgnored?: true
/**
* The fetchMethod promise resolved successfully
*/
fetchResolved?: true
/**
* The results of the fetchMethod promise were stored in the cache
*/
fetchUpdated?: true
/**
* The fetchMethod promise was rejected
*/
fetchRejected?: true
/**
* The status of a {@link get} operation.
*
* - fetching: The item is currently being fetched. If a previous value is
* present and allowed, that will be returned.
* - stale: The item is in the cache, and is stale.
* - hit: the item is in the cache
* - miss: the item is not in the cache
*/
get?: 'stale' | 'hit' | 'miss'
/**
* A fetch or get operation returned a stale value.
*/
returnedStale?: true
}
存储容量安全边界
此实现力求在安全内存消耗和最佳性能的范围内,提供尽可能高的灵活性。
在初始创建对象时,会为 max 个条目预分配存储空间。若 max 设置为零,则会损失部分性能,且条目数量不受限制。若未指定 max,则必须设置 maxSize 或 ttl 中的至少一项。
若设置了 maxSize,则会对最大存储消耗设定安全上限,但无法享受预分配带来的性能优势。当启用 maxSize 时,每个条目必须通过构造函数提供的 sizeCalculation 方法,或通过 cache.set() 提供的 size 或 sizeCalculation 选项来指定大小。每个条目的大小必须为正整数。
若既未设置 max 也未设置 maxSize,则必须启用 ttl 追踪功能。请注意,即使在追踪条目 ttl 时,除非启用 ttlAutopurge,否则条目过期后不会被主动删除,而是仅在下次请求该键时才会被清除。因此,若未设置 ttlAutopurge、max 和 maxSize,缓存可能会无限增长。
此时会向标准错误流打印警告信息。未来版本可能会要求在未指定 max 和 maxSize 时强制使用 ttlAutopurge。
若确实需要仅依赖 TTL 过期机制来限制缓存,建议使用 Map 对象,并通过 setTimeout 在条目过期时删除它们。其性能将显著优于 LRU 缓存。
以下是一个可用的实现方案,采用与本包相同的许可证:
// a storage-unbounded ttl cache that is not an lru-cache
const cache = {
data: new Map(),
timers: new Map(),
set: (k, v, ttl) => {
if (cache.timers.has(k)) {
clearTimeout(cache.timers.get(k))
}
cache.timers.set(
k,
setTimeout(() => cache.delete(k), ttl)
)
cache.data.set(k, v)
},
get: k => cache.data.get(k),
has: k => cache.data.has(k),
delete: k => {
if (cache.timers.has(k)) {
clearTimeout(cache.timers.get(k))
}
cache.timers.delete(k)
return cache.data.delete(k)
},
clear: () => {
cache.data.clear()
for (const v of cache.timers.values()) {
clearTimeout(v)
}
cache.timers.clear()
},
}
如果这不符合您的需求,不妨试试 @isaacs/ttlcache。
存储 Undefined 值
本缓存从不存储 undefined 值,因为在内部实现中,undefined 被用于表示某个键不在缓存中。
您可以调用 cache.set(key, undefined),但这实际上是 cache.delete(key) 的别名。需要注意的是,这样做会导致 cache.has(key) 在将其设置为 undefined 后返回 false。
cache.set(myKey, undefined)
cache.has(myKey) // false!
如果需要追踪 undefined 值,同时仍需标明该键存在于缓存中,一个简单的变通方法是使用自定义的标记对象。
import { LRUCache } from 'lru-cache'
const undefinedValue = Symbol('undefined')
const cache = new LRUCache(...)
const mySet = (key, value) =>
cache.set(key, value === undefined ? undefinedValue : value)
const myGet = (key, value) => {
const v = cache.get(key)
return v === undefinedValue ? undefined : v
}
性能表现
截至2022年1月,该库的第7版是JavaScript中性能最优的LRU缓存实现之一。
性能基准测试极难准确进行。尤其是,对象上set/get/delete操作的性能会因键的类型不同而天差地别。V8引擎对键为短字符串(特别是整数数字字符串)的对象进行了深度优化。因此,任何仅使用数字作为键的基准测试往往会发现基于对象的实现表现最佳。
需注意,将任何值强制转换为字符串作为对象键是不安全的,除非你能100%确定不会使用其他类型的值。例如:
const myCache = {}
const set = (k, v) => (myCache[k] = v)
const get = k => myCache[k]
set({}, 'please hang onto this for me')
set('[object Object]', 'oopsie')
同时,警惕那些关于性能的“想当然”说法。对于大型(尤其是深度)对象图的垃圾回收,其成本可能极其高昂,存在多个“临界点”,一旦超过这些点,成本会呈指数级增长。因此,推迟处理只会让情况变得更糟,且更难以预测。如果一个库在对象图保持浅层时表现良好,但当你使用大型对象作为键时,这种优势将不复存在。
一般来说,当尝试使用库来提升性能(比如像这样的缓存库)时,最好选择一个在实际使用场景中表现优异的选项。
本库针对频繁读取和最小化淘汰时间进行了优化,因为这是 LRU 缓存的典型需求。相比之下,设置操作的平均速度略慢于其他一些选项,部分原因正是出于这种优化。我们假设你会缓存一些高成本操作,且理想情况下尽可能少地执行,因此优先优化读取而非设置是明智之举。
若性能对你至关重要:
-
如果可能,尽量使用小整数值作为键,并确保不会使用其他类型的值作为键。此时,可选用 lru-fast 或 mnemonist 的 LRUCache,它们以对象作为数据存储。
-
若无法满足上述条件,尽可能使用短的非数字字符串(即少于 256 个字符)作为键,并选择 mnemonist 的 LRUCache。
-
如果你的键类型是其他类型(尤其是长字符串、类似浮点数的字符串、对象或混合类型),或者你不确定,那么本库将非常适合你。
若你不需要本库提供的功能(如异步获取、多种 TTL 过期选项等),mnemonist 的 LRUMap 是个非常不错的选择,速度略快于本模块(因为它功能更精简)。
-
除非绝对必要,否则不要使用
dispose函数、大小跟踪或 TTL 行为。这些功能虽方便且在某些场景下必不可少,且已尽力减少性能影响,但并非毫无代价。
版本 7 的重大变更
本库在版本 7 中采用了不同的算法和内部数据结构,显著提升了性能,但也带来了一些细微变化。
如果你依赖版本 6 或更早的 LRUCache 内部实现,很可能无法在版本 7 及以上版本中正常工作。
版本 8 的重大变更
fetchContext选项更名为context,且不能再在缓存实例上直接设置。- 使用 TypeScript 重写,因此几乎所有类型定义都发生了较大变动。
- 移除了 AbortController/AbortSignal 的 polyfill。因此,现在要求 Node 版本不低于 16.14.0。
- 内部属性移至真正的私有类属性。
- 键和值不能为
null或undefined。 - 提供最小化导出路径
'lru-cache/min',支持 CJS 和 MJS 构建。
版本 9 的变更
- 仅提供命名导出,无默认导出。
- 重新引入 AbortController polyfill,但使用时会有警告。
更多信息,请参阅更新日志。