This article demonstrates how you implement a local cache of fetched requests so that if done repeatedly it reads from session storage instead. The advantage of this is that you don't need to have custom code for each resource you want cached.
Follow along if you want to look really cool at your next JavaScript dinner party, where you can show off various skills of juggling promises, state-of-the-art APIs and local storage.
The Fetch API
At this point you're hopefully familiar with fetch. It's a new native API in browsers to replace the old XMLHttpRequest
API.
Where it hasn't been perfectly implemented in all browsers, you can use GitHub's fetch polyfill (And if you have nothing to do all day, here's the Fetch Standard spec).
The Naïve Alternative
Suppose you know exactly which one resource you need to download and only want to download it once. You could use a global variable as your cache, something like this:
let origin = null
fetch('https://httpbin.org/get')
.then(r => r.json())
.then(information => {
origin = information.origin // your client's IP
})
// need to delay to make sure the fetch has finished
setTimeout(() => {
console.log('Your origin is ' + origin)
}, 3000)
That just relies on a global variable to hold the cached data. The immediate problem is that the cached data goes away if you reload the page or navigate to some new page.
Let's upgrade our first naive solution before we dissect its shortcomings.
fetch('https://httpbin.org/get')
.then(r => r.json())
.then(info => {
sessionStorage.setItem('information', JSON.stringify(info))
})
// need to delay to make sure the fetch has finished
setTimeout(() => {
let info = JSON.parse(sessionStorage.getItem('information'))
console.log('Your origin is ' + info.origin)
}, 3000)
The first an immediate problem is that fetch
is promise-based, meaning we can't know for sure when it has finished, so to be certain we should not rely on its execution until its promise resolves.
The second problem is that this solution is very specific to a particular URL and a particular piece of cached data (key information
in this example). What we want is a generic solution that is based on the URL instead.
First Implementation - Keeping It Simple
Let's put a wrapper around fetch
that also returns a promise. The code that calls it probably doesn't care if the result came from the network or if it came from the local cache.
So imagine you used to do this:
fetch('https://httpbin.org/get')
.then(r => r.json())
.then(issues => {
console.log('Your origin is ' + info.origin)
})
And now you want to wrap that, so that repeated network calls can benefit from a local cache. Let's simply call it cachedFetch
instead, so the code looks like this:
cachedFetch('https://httpbin.org/get')
.then(r => r.json())
.then(info => {
console.log('Your origin is ' + info.origin)
})
The first time that's run, it needs to resolve the request over the network and store the result in the cache. The second time it should draw directly from the local storage.
Let's start with the code that simply wraps the fetch
function:
const cachedFetch = (url, options) => {
return fetch(url, options)
}
This works, but is useless, of course. Let's implement the storing of the fetched data to start with.
const cachedFetch = (url, options) => {
// Use the URL as the cache key to sessionStorage
let cacheKey = url
return fetch(url, options).then(response => {
// let's only store in cache if the content-type is
// JSON or something non-binary
let ct = response.headers.get('Content-Type')
if (ct && (ct.match(/application\/json/i) || ct.match(/text\//i))) {
// There is a .json() instead of .text() but
// we're going to store it in sessionStorage as
// string anyway.
// If we don't clone the response, it will be
// consumed by the time it's returned. This
// way we're being un-intrusive.
response.clone().text().then(content => {
sessionStorage.setItem(cacheKey, content)
})
}
return response
})
}
Continue reading %Cache Fetched AJAX Requests Locally: Wrapping the Fetch API%
by Peter Bengtsson via SitePoint
No comments:
Post a Comment