Public
Edited
Apr 12, 2024
Importers
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
viewof foo = cached('appliances', exampleFetcher)
Insert cell
exampleFetcher = async (cacheKey) => {
const response = await fetch(`https://random-data-api.com/api/v2/${cacheKey}`)

return response.json()
}
Insert cell
Insert cell
async function cached(cacheKey, fetcher, defaultValue) {
const internalCacheKey = `${new URL(document.baseURI).pathname}_${cacheKey}`

let cachedValue
let state = 'fetching' // fetching, done, cleared

const label = htl.html`<small style="color: #a0a0a0;"></small>`
const clearButton = htl.html`<button onclick=${() => clear()}>Clear</button>`
const refreshButton = htl.html`<button onclick=${() => refresh()}>Refresh</button>`
const output = htl.html`<output>`
const node = htl.html`
<div style="display: flex; flex-flow: column nowrap; gap: 0.15em;">
<label style="display: flex; align-items: baseline; gap: 0.25em; cursor: text;">
${refreshButton}
<span>or</span>
${clearButton}
<span>cached data at</span>
<code style="display: flex; padding: 0.1em 0.25em; border-radius: 0.25em; background-color: #f5f5f5;">
<span style="color: var(--syntax-known-variable)">IndexedDB</span>
<span style="color: var(--syntax-normal)">[</span>
<span style="color: var(--syntax-string)">'${internalCacheKey}'</span>
<span style="color: var(--syntax-normal)">]</span>
</code>
</label>
${label}
${output}
</div>
`
const updateDisplay = (state, value) => {
if (state === 'fetching') {
clearButton.disabled = true
refreshButton.disabled = true
label.textContent = 'Refreshing...'
output.replaceChildren(inspect(defaultValue))
} else if (state === 'cleared') {
clearButton.disabled = true
refreshButton.disabled = false
label.textContent = 'Cache cleared'
output.replaceChildren(inspect(defaultValue))
} else if (state === 'done') {
clearButton.disabled = false
refreshButton.disabled = false

const lastUpdated = new Date(value.timestamp)
label.textContent = `Last updated at ${lastUpdated.toLocaleDateString()} ${lastUpdated.toLocaleTimeString()}`
output.replaceChildren(inspect(value.data))
}
}

const update = (state, value) => {
cachedValue = value
node.value = value?.data
node.dispatchEvent(new Event("input", { bubbles: true }));
updateDisplay(state, value)
}
Object.defineProperty(node, "value", {
get() {
return cachedValue?.data;
},
set(value) {}
});

const clear = async () => {
update('cleared')

await localforage.removeItem(internalCacheKey)
}

const refresh = async () => {
update('fetching')
const data = await fetcher(cacheKey)
const cacheValue = {timestamp: Date.now().valueOf(), data}

await localforage.setItem(internalCacheKey, JSON.stringify(cacheValue))

update('done', cacheValue)
}

const initialValue = await localforage.getItem(internalCacheKey)
if (initialValue) {
update('done', JSON.parse(initialValue))
} else {
refresh()
}

return node
}
Insert cell
Insert cell
import {inspect} from "@observablehq/inspector"
Insert cell
localforage = import('https://cdn.skypack.dev/localforage')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more