Cache me if you can !!
With the fast and vast ongoing popularity and adoption of PWAs, soon the norm is going to be Web Apps and PWAs mostly around with a lesser number of traditional website modal web portals.
I see people often misinterpret PWA is all about Service Workers, well it is but Service Workers are more like the backbone of it’s capabilities while PWA itself is made using a bunch of different Web APIs together to extract some sensible feature out of it. One of these APIs is the CacheStorage
API which is also our point of discussion. This article assumes you’ve basic familiarity with Service Workers, it’s lifecycles and offline web apps, if not, I would suggest you to take a look at this cool thing the web has for quite some time now!
When we think of a PWA, the impression that comes first to our mind is offline. The idea behind this is that once the Service Worker gets installed and activates, it can intercept network requests made from our origin or “the service worker scope” and serve contents for our app to the browser from some local cache. But before all that, some secure mechanism is required to manage storing, retrieving and updating app related content. Here comes the requirement and role of CacheStorage
API. Caching and serving assets from the local system not only provides better UX when a device is offline or in slow network area but also helps reducing data usage and bandwidth on client and server respectively.
CacheStorage
enables the application to store a request-response list under category keys. This means you can define categories of data you want to store for your app, and then under those categories or keys, store a list of requests
mapped to their corresponding responses
for future use. The asynchronous, Promise
based nature of CacheStorage
and design & behaviour of Service Worker events and lifecycle along with fetch
which is also Promise
base API makes them such good fit to use together that it has lead to a series of patterns to store and serve contents depending on their role and requirement to the app. But even without Service Workers
, CacheStorage
is still a good fit for storage purposes, mainly because of it’s nonblocking async nature.
Although CacheStorage
is defined under the Service Worker spec, it falls underWindowOrWorkerGlobalScope,
which means it can be accessed from the window
or any type of worker
scope (e.g. Service Worker, Web Worker, Shared Worker).
Now that we have ground understanding of what CacheStorage
API is, let’s look into how to use it using the functions it exposes to the user and then some patterns around it.
Before we start, I want us to be clear about few terminologies-
CacheStorage
— the API itself. accessible with variablecaches
onwindow
as well asworker
scope.Cache
— an object representing one single category or request-response list stored underCacheStorage
.
Generally while caching resources you’d want to categorize them according to type or usage such as static assets, image resources, dynamic API responses.
Creating/Opening cache in store
Your domain/origin can have multiple named cache
objects in the store. The caches
object that implements CacheStorage
has a method, open
. open
takes in CacheName
as a parameter and returns a Promise
which resolves with a cache
object. If a cache
of the same name already exists, open
returns reference to the cache
, otherwise, it creates a new cache
and returns it’s reference.
Retrieving all cache keys from store
caches.keys()
resolves with a Promise
containing all Cache names or category names in a sequence. This method is handy to retrieve all caches when Service Worker is updated and delete all non required data from the previous version, helps in keeping origin cache clean.
Checking if a named cache exists in store
caches.has
is a function exposed by CacheStorage
that takes in cacheName as a parameter and returns a Promise
that resolves in to true
or false
depending on whether the CacheStorage
contains any cache with the supplied name.
Retrieving items from store
CacheStorage
has a match
method, accessed through caches.match
. match
takes a required parameter RequestInfo
and an optional CacheQueryOptions
parameter. RequestInfo
is the request for which the response is stored. For example, an image resource can be stored against request with URL user/abc/profile
with Content-Type image/jpeg
.CacheQueryOptions
are set of configurations to alter the filtering w.r.t providedRequestInfo
. It has four values-
- ignoreSearch- defaults to false. If set to true, indicates that we wish to exclude the query params from
RequestInfo
URL. - ignoreMethod- defaults to false. If set to true, indicates that we wish to prevent the matching operation from validating the
Request
method.
Note: onlyGet
andHead
are stored in thecache
. - ignoreVary- defaults to false. If set true, indicates that we wish to include responses with VARY headers as well.
- cacheName- name of the specific
Cache
, we want to look into instead of looking through allcache
in storage.
Deleting from store
caches.delete
takes a string i.e. cacheName and returns a Promise
that resolves to true
or false
depending on whether the operation was successful or not. You can use this when your site’s Service Worker gets updated to clear up the whole old version assets cache that is no longer required.
If you’ve used databases before, then you can relate with caches
object or CacheStorage
is the container or the database itself and the object that caches.open
resolves into i.e.cache
represents one table in a database.
Cache
interface is very much similar to that of CacheStorage,
let’s have a look at cache
as well.
Add items to cache
cache.add
method accepts arequest
. From the passed request,response
is fetched over the network and then stored as a value against request as the key.- Another similar method is available,
cache.addAll
which takes an array ofRequest
that is fetched over the network and respectiveresponse
is stored against theirRequests
in store. These are helpful in prefetching static resources of our site. Often used with Service Worker update event to update new app resources.
put
—cache.put
allows to pass bothrequest
andresponse
objects that are stored as key and value respectively. Often you’d usecache.put
in Service Worker’s fetch event handler to store responses in cache for serving in future.
Retrieve items from cache
match
— match takes aRequestInfo
and an optionalCacheQueryOptions
as parameters and returns the first matchingobject
which can be of any type if found orundefined
if no match is found, wrapped inPromise
. This is same asCacheStorage.match
the only difference is that this is called oncache
object and hence cacheName option inCacheQueryOptions
is ignored in this call.matchAll
— matchAll takes aRequestInfo
and an optionalCacheQueryOptions
as parameters and returns aPromise
that resolves into FrozenArray of all responses from the cache that matches this request. This is same asCacheStorage.match
the only difference is that this is called oncache
object and hence cacheName option inCacheQueryOptions
is ignored in this call.
match
andmatchAll
here are called oncache
object so the search operation is executed only in the specificcache
, not the wholeCacheStorage
Retrieving all keys from cache
cache.keys()
resolves in a Promise
containing Frozen array of Request
. These are all the keys from the cache
can be used to iterate and filter objects in store or clean up some items.
Deleting from cache
cache.delete
takes a Request
object and an optional CacheQueryOptions
to identify and delete some request-response pair from the cache
. Since CacheStorage
performs all operations manually, i.e. only when called by the user, it is very critical to wisely use delete
method and clear up any data that is no longer relevant in the cache
. delete
resolves into a Promise
of true
or false
depending on the operation success.
That’s all about the CacheStorage
API. Future of this API looks promising with support in major browsers such as Chrome, Edge, FireFox, and Opera. Safari also landed the CacheStorage
API in Preview 43. Internet Explorer is the only one with a red flag for API.
For older browser that does not support
Cache
API, you can place a check
if(‘caches’ in window){/*use caches*/}For offline web features,
CacheStorage
have evolved few patterns to handle different type of data based on priority are really handy to start with and should cover all general scenario. Since those features make usage of other APIs withCacheStorage
, I’ve made a separate article Cache Recipes for that. Please head over to the next one after you are finished with this article.
With all that being said, serving fast and light cost data is way more important than it looks on 4g test devices and we as developers, are responsible to push all the native builtin superpowers of the awesome web platform to the end users and provide the best experience available. This contributes to our product, user experience and ultimately the Web Ecosystem❤️.
⚡⚡⚡#letsPushSuperPowers🔥🔥🔥