stacker.news/lib/fetch.js

71 lines
1.8 KiB
JavaScript

export async function fetchWithTimeout (resource, { timeout = 1000, ...options } = {}) {
const controller = new AbortController()
const id = setTimeout(() => controller.abort(), timeout)
const response = await fetch(resource, {
...options,
signal: controller.signal
})
clearTimeout(id)
return response
}
class LRUCache {
constructor (maxSize = 100) {
this.maxSize = maxSize
this.cache = new Map()
}
get (key) {
if (!this.cache.has(key)) return undefined
const value = this.cache.get(key)
// refresh the entry
this.cache.delete(key)
this.cache.set(key, value)
return value
}
set (key, value) {
if (this.cache.has(key)) this.cache.delete(key)
else if (this.cache.size >= this.maxSize) {
// Remove the least recently used item
this.cache.delete(this.cache.keys().next().value)
}
this.cache.set(key, value)
}
}
export function cachedFetcher (fetcher, { maxSize = 100, cacheExpiry, forceRefreshThreshold, keyGenerator }) {
const cache = new LRUCache(maxSize)
return async function cachedFetch (...args) {
const key = keyGenerator ? keyGenerator(...args) : JSON.stringify(args)
const now = Date.now()
async function fetchAndCache () {
const result = await fetcher(...args)
cache.set(key, { data: result, createdAt: now })
return result
}
const cached = cache.get(key)
if (cached) {
const age = now - cached.createdAt
if (cacheExpiry === 0 || age < cacheExpiry) {
return cached.data
} else if (forceRefreshThreshold === 0 || age < forceRefreshThreshold) {
fetchAndCache().catch(console.error)
return cached.data
}
} else if (forceRefreshThreshold === 0) {
fetchAndCache().catch(console.error)
return null
}
return await fetchAndCache()
}
}