The Cache API is a new interface that comes together with the Service Worker to the browsers. The main purpose is to enable Service Workers to cache network requests so they can send back responses to foreground scripts even when the browser is offline.
It is primarily designed for storing network requests and their responses, but you can store anything in the cache and use it as a general-purpose storage mechanism.
The main entry point to the API is the global caches
object. The API is not limited to Service Workers. You can access caches
from a foreground script (window), a Web Worker, and iframe. All have access to the same caches from the same origin.
CacheStorage ¶
caches
is an instance of type CacheStorage and provides the following 5 methods.
All methods in the Cache API are asynchronous and return a Promise.
open(cacheName)
Opens or creates a cache. An origin can have multiple named caches. If the specified cache does not exist, a new cache is created with the provided cacheName
.
const cache = await caches.open('testCache');
has(cacheName)
Checks if a cache with the specified cacheName
exists. Returns true when it exists.
const exist = await caches.has('testCache');
// true
delete(cacheName)
Deletes the cache with the specified cacheName
. When the cache exist, deletes the cache and returns true, otherwise returns false.
const deleted = await caches.delete('testCache');
// true
keys()
Returns the names (string) of all available caches in an array.
const cacheNames = await caches.keys();
// ["testCache"]
match(request, options)
This method is a convenience method that calls the match()
method on each existing cache. See cache.match()
description a bit further below.
This method processes the caches in the same order as caches.keys()
returns the names.
The second parameter is optional and supports the same options as cache.match()
to control the matching process.
This method supports one additional option: cacheName
specifies the cache to search within.
const chuckCache = await caches.open('chuckCache');
chuckCache.addAll([
'https://api.icndb.com/jokes/15',
'https://api.icndb.com/jokes/16',
'https://api.icndb.com/jokes/17'
]);
const response1 = await caches.match('https://api.icndb.com/jokes/15', {cacheName: 'chuckCache'});
const response2 = await chuckCache.match('https://api.icndb.com/jokes/15');
These two match calls are equivalent. caches.match()
is useful when you have many caches and need to search a resource in all of them.
Cache ¶
The Cache object that caches.open()
returns provides the following seven methods. All of these methods work with Request and Response objects.
Instead of a Request, you can provide a string. The Cache API internally converts the string to a Request object with new Request(string)
.
put(request, response)
Adds a key-value pair to the cache. put()
overwrites an existing entry with the same key.
const chuckCache = await caches.open('chuckCache');
const url = 'https://api.icndb.com/jokes/23';
const response = await fetch(url);
chuckCache.put(url, response);
add(request)
addAll([request, request, request, ...])
Takes one or more URLs, fetches them and adds the response(s) to the cache.
const chuckCache = await caches.open('chuckCache');
chuckCache.add('https://api.icndb.com/jokes/24');
chuckCache.addAll([
'https://api.icndb.com/jokes/25',
'https://api.icndb.com/jokes/26',
'https://api.icndb.com/jokes/27'
]);
add()
is equivalent to the following code
const response = await fetch(url);
if (response.ok) {
await cache.put(url, response);
}
add()
and addAll()
do not cache responses with a status that is not in the 200 range. put()
on the other hand stores any request/response pair.
match(request, options)
Returns a response object associated with the first matching request. Returns undefined
when no matching request exist. The second parameter is optional and is an object with options that control the matching process. The following options are supported:
- ignoreSearch: boolean, default false. Specifies whether the matching process should ignore the query string in the URL. For example, if you have an entry https://test.com/img1.png and you search with https://test.com/img1.png?someValue=test it would not find the entry, but setting this option to true would find the entry.
- ignoreMethod: boolean, default false. If set to true, the matcher ignores the HTTP method.
- ignoreVary: boolean, default false. If set to true, the matching operation ignores the VARY header. When the URL matches it returns the match regardless of the Response object has a VARY header or not.
matchAll(request,options)
Works the same as match()
, but instead of returning the first response that matches (response[0]
), matchAll()
returns all matching responses in an array. You can also call matchAll()
without any parameter, and the method returns all cached responses.
const chuckCache = await caches.open('chuckCache');
await chuckCache.add('https://api.icndb.com/jokes/30');
await chuckCache.add('https://api.icndb.com/jokes/40');
await chuckCache.add('https://api.icndb.com/jokes/50');
const first = await chuckCache.match('https://api.icndb.com/jokes/30');
// Response {type: "cors", url: "https://api.icndb.com/jokes/30"
const all = await chuckCache.matchAll()
/*
[
Response {type: "cors", url: "https://api.icndb.com/jokes/30", …}
Response {type: "cors", url: "https://api.icndb.com/jokes/40", …}
Response {type: "cors", url: "https://api.icndb.com/jokes/50", …}
]
*/
delete(request,options)
Returns true when it found a matching entry and deleted it, otherwise returns false.
const chuckCache = await caches.open('chuckCache');
const request = new Request('https://api.icndb.com/jokes/24');
await chuckCache.add(request);
const deleted = await chuckCache.delete(request)
// true
This method also takes the same options object as cache.match()
as the second optional parameter, which allows you to delete multiple Request/Response pairs for the same URL.
keys(request,options)
Returns an array of keys (requests). You can either call the keys()
method without a parameter then it returns all keys that are stored in the cache or you can specify a request and the method only returns matching keys. keys()
support the same options as the cache.match()
method as the second optional parameter. The keys are returned in the same order that they were inserted.
const chuckCache = await caches.open('chuckCache');
await chuckCache.add('https://api.icndb.com/jokes/30');
await chuckCache.add('https://api.icndb.com/jokes/40');
await chuckCache.add('https://api.icndb.com/jokes/50');
const allKeys = await chuckCache.keys();
/*
[
Request {method: "GET", url: "https://api.icndb.com/jokes/30", …},
Request {method: "GET", url: "https://api.icndb.com/jokes/40", …}
Request {method: "GET", url: "https://api.icndb.com/jokes/50", …}
]
*/
const oneKey = await chuckCache.keys('https://api.icndb.com/jokes/40')
/*
[
Request {method: "GET", url: "https://api.icndb.com/jokes/40", …}
]
*/
General purpose cache ¶
In the previous section, all the examples use the Fetch API to request a resource from a server and then store the response in the cache. The Cache API is primarily built for this use case and therefore, can only store a Request object as the key and a Response object as the value.
But Request and Response objects can contain any data that can be transferred over HTTP. The Response constructor supports different types like Blobs, ArrayBuffers, FormData, and strings.
The following example maps a string key to a string value
const languageCache = await caches.open('languages');
await languageCache.put('de', new Response('German'));
await languageCache.put('en', new Response('English'));
await languageCache.put('fr', new Response('French'));
const allKeys = await languageCache.keys();
/*
[
Request {method: "GET", url: "http://localhost:8100/de", …}
Request {method: "GET", url: "http://localhost:8100/en", …}
Request {method: "GET", url: "http://localhost:8100/fr", …}
]
*/
const en = await languageCache.match('en');
const enText = await en.text();
// English
const deleted = await languageCache.delete('fr');
// true
const fr = await languageCache.match('fr');
// undefined
Example with JSON.
const usersCache = await caches.open('users');
await usersCache.put('1', new Response('{"id": 1, "name": "John", "age": 27 }'));
const user1 = await usersCache.match('1');
const user1json = await user1.json();
// {id: 1, name: "John", age: 27}
The Response object provides several methods to access the body: arrayBuffer()
, blob()
, text()
, json()
, formData()
.
Depending on the type of the Response body, you need to call the appropriate method. All these methods are asynchronous and return a Promise.
Example ¶
As mentioned at the beginning of the blog post, the global caches
object is not only accessible in the Service Worker; you can also access it from the window
object. And both have access to the same caches of the same origin.
This example shows you a use case for that. It's an Ionic app that fetches and stores four pictures in the Service Worker and then in the foreground script accesses the cache and displays all stored pictures.
This could be useful for applications that support an offline mode and need a way to only display items that are cached.
Implementation ¶
The Service Worker listens for the install
event and then calls the loadPictures()
method.
In that method, it creates a cache images
, fetches and stores four pictures with the cache.addAll()
method.
After that, it sends a message imagesCached
to all clients that are associated with this Service Worker.
self.addEventListener('install', event => event.waitUntil(loadPictures()));
async function loadPictures() {
const cache = await caches.open('images');
const pictures = [
'https://omed.hplar.ch/img/pexels-photo-127753.jpeg',
'https://omed.hplar.ch/img/pexels-photo-132037.jpeg',
'https://omed.hplar.ch/img/pexels-photo-248771.jpeg',
'https://omed.hplar.ch/img/pexels-photo-248797.jpeg'
];
await cache.addAll(pictures);
const allClients = await clients.matchAll({includeUncontrolled: true});
for (const client of allClients) {
client.postMessage('imagesCached');
}
}
In the Ionic app, we call the listCache()
method from the ngOnInit
method. The very first time when listCache()
is called, it is possible that the cache is empty because the Service Worker is not installed yet or it is installed, but the images are not downloaded yet.
Therefore, we install a message event listener and wait for the imagesCached
message. The Service Worker sends this message as soon as all pictures are fetched and stored. When that happens, we can call the listCache()
method to display the pictures. The message handler is called each time we install a new Service Worker.
import {Component, OnInit} from '@angular/core';
import {IonicSlides} from '@ionic/angular';
@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss']
})
export class HomePage implements OnInit {
swiperModules = [IonicSlides];
pictures: string[] = [];
constructor() {
navigator.serviceWorker.addEventListener('message', event => {
if (event.data === 'imagesCached') {
this.listCache();
}
});
}
ngOnInit(): void {
this.listCache();
}
async listCache(): Promise<void> {
this.pictures = [];
const cache = await caches.open('images');
const responses = await cache.matchAll();
for (const response of responses) {
const ab = await response.arrayBuffer();
// @ts-ignore
const imageStr = 'data:image/jpeg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(ab)));
this.pictures.push(imageStr);
}
}
}
The listCache()
method opens the images
cache, retrieves all entries with matchAll()
and then loops over each response with forEach
.
Inside the loop, it extracts the ArrayBuffer of the Response and creates a data URL. On the HTML template, it loops over all these data URLs and creates an img tag for each entry.
<ion-header>
<ion-toolbar>
<ion-title>
Cache Example
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<swiper-container [modules]="swiperModules" [pagination]="true">
<swiper-slide *ngFor="let pic of pictures"><img [src]="pic" alt="pic"></swiper-slide>
</swiper-container>
</ion-content>
You find the source code for this example on GitHub: https://github.com/ralscha/blog/tree/master/sw-cache
For further information about the Cache API, visit these pages: