Go Fetch 2! (JavaScript Fetch API)

Go Fetch 2! (JavaScript Fetch API)

Last time we discussed the Fetch API in general, taking a look at how it differed from the XMLHttpRequest API, and some of its advantages. Today, we’re going to take a look at a little library that you can include in your projects today that offers you localStorage caching for the Fetch API.

 

TL;WR

If you’re anxious to get to the good stuff, you can head straight to the fetchCache Github Repo.

 

Cache Control

Before we get started, those of you more familiar with the Fetch API might be wondering why we need to use localStorage for caching fetch request responses at all. Isn’t that what the cache property in the fetch settings is for?

Yes and no. The cache setting allows you greater control over the standard HTTP cache, so that you don’t need to use the old trick of appending a datetime string to your request for ‘cache-busting’ purposes, as you might have done in the past with XHR. Instead, you can simply specify cache:'no-store' or cache:'no-cache' or cache:'reload', depending on your use case.

You can also bypass a network request (potentially useful for offline usage) and pull from the HTTP cache via cache:'force-cache', which appears to accomplish one of the goals of caching in localStorage.

However, that’s as far as our control over the cache goes. We can’t specify how long we want items to remain in the cache, or what requests line up with which contents of the cache, or validate the contents of the cache before returning them. For that, we need to wrap fetch with a bit of custom logic to allow us to handle the caching ourselves.

 

Looks Familiar

Just before we dive in, you may find that the API I’m about to present looks familiar – maybe because you’ve used JALC, the plugin for similar caching with jQuery.ajax. The API similarity is most definitely on purpose, and you should feel comfortable moving from one to the other.

 

The Breakdown

So, let’s take a look at the code, and discuss what’s happening.

For those of you who’ve used JALC, much of this will look familiar, and you can reference this previous breakdown of the core parts of JALC for more details, so we’ll skip over those. Let’s take a look at our cacheResponse and provideResponse functions, as they’re the juicy bits.

   /**
	* Cache the response into our storage object.
	* We clone the response so that we can drain the stream without making it
	* unavailable to future handlers.
	*
	* @param {string} cacheKey Key under which to cache the data string. Bound in
	* fetch override.
	* @param {Storage} storage Object implementing Storage interface to store cached data
	* (text or json exclusively) in. Bound in fetch override.
	* @param {Number} hourstl Number of hours this value shoud remain in the cache.
	* Bound in fetch override.
	* @param {Response} response
	*/
function cacheResponse(cacheKey, storage, hourstl, response) {
	var cres = response.clone(),
		dataType = (response.headers.get('Content-Type') || 'text/plain').toLowerCase();

	cres.text().then((text) => {
		try {
			storage.setItem(cacheKey, text);
			storage.setItem(cacheKey + 'cachettl', +new Date() + 1000 * 60 * 60 * hourstl);
			storage.setItem(cacheKey + 'dataType', dataType);
		} catch (e) {
			// Remove any incomplete data that may have been saved before the exception was caught
			removeFromStorage(storage, cacheKey);
			console.log('Cache Error: ' + e, cacheKey, text);
		}
	});

	return response;
}

The interesting bit here is you’ll see we clone the response. This is so we can ‘drain’ it with the text reader and store the results, without making the response unavailable to future promise handler blocks. By cloning the response, we get a buffered clone of the response value that we can do with as we wish, and we can pass the original response on to be handled however the future then blocks wish to.

   /**
	* Create a new response containing the cached value, and return a promise
	* that resolves with this response.
	*
	* @param value
	* @param dataType
	* @returns {Promise}
	*/
function provideResponse(value, dataType) {
	var response = new Response(
		value,
		{
			status: 200,
			statusText: 'success',
			headers: {
				'Content-Type': dataType
			}
		}
	);

	return new Promise(function (resolve, reject) {
		resolve(response);
	});
}

Here’s the last really interesting bit, and it demonstrates the advantage of having access to the Response primitive – we can create our own streamable response by simply feeding in the value to the Response constructor, and return a new Promise as the fetch API expects immediately resolving with our response.

 

All Together Now

Here’s the full code as of the time of this writing, and I recommend taking a look at the github repo for the up-to-date version. As always, let me know if you have any questions in the comments.

Image Courtesy of publicdomainpictures.net

Christopher Keefer

Christopher Keefer

Christopher Keefer is a Senior Software Engineer at Art & Logic. He generally spends his spare time on the computer too, so there isn't much hope for him.
Christopher Keefer

Latest posts by Christopher Keefer (see all)

Tags:

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.