-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
69 lines (54 loc) · 2.64 KB
/
index.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
const LRU = require('lru-cache')
module.exports = ({ maxAge, validate, staleWhileRevalidate, ...rest }) => {
if (!maxAge) throw new Error('maxAge needs to be defined in stale-while-revalidate-lru-cache')
if (!staleWhileRevalidate) throw new Error('staleWhileRevalidate needs to be defined in stale-while-revalidate-lru-cache')
if (!validate) throw new Error('validate function needs to be defined in stale-while-revalidate-lru-cache')
// Use lru-cache as it removes the cached values least used
const cache = new LRU({ ...rest })
return async ({ key, params }) => {
const cachedItem = cache.has(key) && cache.peek(key)
const isCached = cachedItem && !cachedItem.isValidating
const isValidating = cachedItem && cachedItem.isValidating
const isStale = isCached && cachedItem.lastValidation + maxAge < Date.now()
const isTooOldToReturn = isCached && ((cachedItem.lastValidation + maxAge + staleWhileRevalidate) < Date.now())
// Is not in cache
if (!isCached && !isValidating) {
cache.set(key, {
validationPromises: []
})
return validateCache({ key, params })
}
if (!isCached && isValidating) return new Promise((resolve, reject) => cache.peek(key).validationPromises.push([resolve, reject]))
// Is in cache, and not stale
if (!isStale) return cache.get(key).value
// Is in cache, and is stale, but not older than staleWhileRevalidate, so cache can be returned
if (!isTooOldToReturn) {
if (!isValidating) validateCache({ key, params })
return cache.get(key).value // return dirty value
}
// Is in cache, is stale, and older than staleWhileRevalidate, so validate cache, before returninga
if (!isValidating) return validateCache({ key, params })
return new Promise((resolve, reject) => cache.peek(key).validationPromises.push([resolve, reject]))
}
async function validateCache ({ key, params }) {
const cachedItem = cache.peek(key)
const { validationPromises } = cachedItem
cachedItem.isValidating = true
try {
const value = await validate(params)
cachedItem.value = value
cachedItem.validationPromises = []
cachedItem.isValidating = false
cachedItem.lastValidation = Date.now()
// Resolve the other validate after this one has returned.
// It guarantees the same order as the cacher was called
process.nextTick(() => validationPromises.forEach(([resolve]) => resolve(value)))
return value
} catch (err) {
// Remove from cache, as it will need to be refetched
cache.del(key)
process.nextTick(() => validationPromises.forEach(([_, reject]) => reject(err)))
throw err
}
}
}