index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { LRFUExpirer, EXPIRED_ENTRY } from './LRFUExpirer.js'
  2. export { LRFUExpirer } from './LRFUExpirer.js'
  3. let defaultExpirer
  4. export class WeakLRUCache extends Map {
  5. constructor(options) {
  6. super()
  7. this.hits = 0
  8. this.misses = 0
  9. if (options && options.cacheSize) {
  10. options.lruSize = options.cacheSize >> 2
  11. }
  12. if (options && options.clearKeptInterval) {
  13. this.clearKeptInterval = options.clearKeptInterval
  14. this.clearKeptCount = 0
  15. this.clearKeptObjects = options.clearKeptObjects
  16. }
  17. this.expirer = (options ? options.expirer === false ? defaultNoLRUExpirer : options.expirer : null) || defaultExpirer || (defaultExpirer = new LRFUExpirer(options))
  18. this.deferRegister = Boolean(options && options.deferRegister)
  19. let registry = this.registry = new FinalizationRegistry(key => {
  20. let entry = super.get(key)
  21. if (entry && entry.deref && entry.deref() === undefined)
  22. super.delete(key)
  23. })
  24. }
  25. onRemove(entry) {
  26. let target = entry.deref && entry.deref()
  27. if (target) {
  28. // remove strong reference, so only a weak reference, wait until it is finalized to remove
  29. this.registry.register(target, entry.key)
  30. entry.value = undefined
  31. } else if (entry.key) {
  32. let currentEntry = super.get(entry.key)
  33. if (currentEntry === entry)
  34. super.delete(entry.key)
  35. }
  36. }
  37. get(key, mode) {
  38. let entry = super.get(key)
  39. let value
  40. if (entry) {
  41. this.hits++
  42. value = entry.value
  43. if (value === EXPIRED_ENTRY) {
  44. value = entry.deref && entry.deref()
  45. if (value === undefined)
  46. super.delete(key)
  47. else {
  48. entry.value = value
  49. if (this.clearKeptInterval)
  50. this.incrementClearKeptCount()
  51. if (mode !== 1)
  52. this.expirer.used(entry)
  53. return mode === 2 ? value : entry
  54. }
  55. }
  56. else {
  57. if (mode !== 1)
  58. this.expirer.used(entry)
  59. return mode === 2 ? value : entry
  60. }
  61. } else
  62. this.misses++
  63. }
  64. getValue(key) {
  65. return this.get(key, 2)
  66. }
  67. setValue(key, value, expirationPriority) {
  68. let entry
  69. if (value && typeof value == 'object') {
  70. entry = new WeakRef(value)
  71. if (this.clearKeptInterval)
  72. this.incrementClearKeptCount()
  73. entry.value = value
  74. if (this.deferRegister) {
  75. entry.key = key
  76. entry.cache = this
  77. } else
  78. this.registry.register(value, key)
  79. } else if (value !== undefined)
  80. entry = { value, key, cache: this }
  81. // else entry is undefined
  82. this.set(key, entry, expirationPriority)
  83. return entry
  84. }
  85. incrementClearKeptCount() {
  86. if (++this.clearKeptCount >= this.clearKeptInterval) {
  87. this.clearKeptCount = 0
  88. if (this.clearKeptObjects)
  89. this.clearKeptObjects()
  90. if (this.registry.cleanupSome)
  91. this.registry.cleanupSome()
  92. }
  93. }
  94. set(key, entry, expirationPriority) {
  95. let oldEntry = super.get(key)
  96. if (oldEntry)
  97. this.expirer.delete(oldEntry)
  98. return this.insert(key, entry, expirationPriority)
  99. }
  100. insert(key, entry, expirationPriority) {
  101. if (entry) {
  102. this.expirer.used(entry, expirationPriority)
  103. }
  104. return super.set(key, entry)
  105. }
  106. delete(key) {
  107. let oldEntry = super.get(key)
  108. if (oldEntry) {
  109. this.expirer.delete(oldEntry)
  110. }
  111. return super.delete(key)
  112. }
  113. used(entry, expirationPriority) {
  114. this.expirer.used(entry, expirationPriority)
  115. }
  116. clear() {
  117. for (let [ key, entry ] of this) {
  118. this.expirer.delete(entry)
  119. super.delete(key)
  120. }
  121. }
  122. }
  123. class NoLRUExpirer {
  124. used(entry) {
  125. if (entry.cache)
  126. entry.cache.onRemove(entry)
  127. else if (entry.deref) // if we have already registered the entry in the finalization registry, just mark it expired from the beginning
  128. entry.value = EXPIRED_ENTRY
  129. }
  130. delete(entry) {
  131. // nothing to do here, we don't have a separate cache here
  132. }
  133. }
  134. const defaultNoLRUExpirer = new NoLRUExpirer()