forked from svt/bridge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCache.js
136 lines (121 loc) · 2.94 KB
/
Cache.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT
const InvalidArgumentError = require('../error/InvalidArgumentError')
/**
* @class Cache
* @description A simple in-memory cache
* that can store any type
* of data
*/
class Cache {
/**
* @private
* A map containing the cache's index,
* mapping keys to cache entries
*
* @typedef {{
* status: 'pending' | undefined,
* promise: Promise | undefined,
* value: any
* }} Entry
*
* @type { Map.<String, Entry> }
*/
#index = new Map()
/**
* @private
* @type { Number }
*/
#maxEntries
constructor (maxEntries = 10) {
this.#maxEntries = maxEntries
}
/**
* @private
* Prepare the cache index for insertion,
* this will make sure that the index
* stays within the set max size
*/
#prepareIndexForInsertion () {
if (this.#index.size < this.#maxEntries) {
return
}
const firstKey = this.#index.keys().next().value
this.#index.delete(firstKey)
}
/**
* Get a cached value
* by its key
* @param { String } key
* @returns { Promise.<any> }
*/
get (key) {
const entry = this.#index.get(key)
/*
If there is a pending promise for the value,
return that rather than starting a new request
*/
if (
entry &&
entry.status === 'pending' &&
entry.promise
) {
return entry.promise
}
return Promise.resolve(this.#index.get(key)?.value)
}
/**
* Cache the response of a provider function,
* the response will be returned if there's
* a valid entry in the cache index
*
* If there's a cache hit,
* the provider function
* will not be called
*
* If the cache is waiting for the provider
* while a new request is made, a promise will
* be returned to the first request value,
* avoiding multiple simultaneous requests
* for the same data
*
* @param { String } key
* @param { Function.<Promise.<any>> } provider
* @returns { Promise.<any> }
*/
async cache (key, provider) {
if (typeof key !== 'string') {
throw new InvalidArgumentError('The provided key must be a string')
}
if (typeof provider !== 'function') {
throw new InvalidArgumentError('The provided provider must be a function')
}
if (this.#index.has(key)) {
return this.get(key)
}
/*
Create a promise that can be used
to return the fetched value to all
waiting recipients
*/
let resolve
let reject
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
this.#prepareIndexForInsertion()
this.#index.set(key, { status: 'pending', promise })
try {
const value = await provider()
this.#index.set(key, { value })
resolve(value)
} catch (e) {
reject(e)
this.#index.delete(key)
}
return promise
}
}
module.exports = Cache