2.2 Cache Files and Responses
# Files and Blobs
When we are going to save things with the Cache API we need to have a Response object and inside each Response object is a File.
A File and a Blob (Binary Large OBject) are essentially the same thing. Both are a container for a bunch of binary data. The difference is that a File has a set of defined properties - file name, file type, file size. The file type is the Mime-Type. Eg: 'application/json', 'text/css', 'image/png'.
A Blob or a File can be created from a typedArray or ArrayBuffer. A File can also come from a form that includes an <input type="file" />. When you get the value of an input with type="file", you
will get a FileList object that could be empty or it could contain one or more file objects.
If you have built or received a TypedArray or ArrayBuffer then you can build your own Blob or File. You can even turn a Blob into a File by passing the Blob to a File constructor and providing a file name and type.
let file = new File([data], filename, { type: 'text/plain', lastModified: Date.now() });
let blob = new Blob([data]);
let file2 = new File([blob], 'somefile.txt', { type: 'text/plain' });
2
3
For more detailed information about creating and working with Blobs and Files watch these two videos.
# Request and Response Objects
When we make fetch calls we get back a Response object. From the Response we can get the status code, the status message, and we can extract the json data, the text or the Blob.
fetch(url)
.then((response) => {
//response is a Response object
if (!response.ok) throw new Error(response.statusText);
console.log(response.status);
console.log(response.statusText);
return response.json();
return response.blob();
return response.text();
})
.then((obj) => {
//obj would be an object built from the JSON string in the response
//or the Blob extracted from the response
//or the text content of a css, js, html, txt, rtf, or xml file
})
.catch((err) => {
console.warn(err.message);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
So, if our Response object comes from a fetch, then it is easy enough to put that Response into a Cache.
However, if you have a File that comes from some other source, like a file input, then we need to create a Response object ourselves.
let filelist = document.getElementById('myAvatarImage').value;
let file = filelist[0];
let h = new Headers();
h.append('Content-Type', file.type);
h.append('Content-Length', file.size);
h.append('X-custom-header', 'some value');
let response = new Response(file, {
status: 200,
statusText: 'All Good',
headers: h,
});
2
3
4
5
6
7
8
9
10
11
Once you have the Response object, then you can put it into your Cache.
For more details watch the short video below (same as the video in Module 2.1).
# Cache API
Added as part of HTML5, the Cache API lets javascript developers intentionally save files, including data files, in the browser to improve performance and reduce latency for repeated future visits.
The Cache API is separate from the built-in browser cache that is controlled by the browser and HTTP Request and Response Headers.
When building a cache, the main reason you would want one is to be able to run your website when the network fails or the user turns off their wifi. We want our app to be able to run offline after the initial load.
While the Cache API is primarily used from Service Workers for Progressive Web Apps, we can also use it directly from any webpage. (We will be talking about Service Workers soon).
It is based on Promises (opens new window), just like the Fetch API.
The following video gives examples of how to pull files from forms, how to extract files from the Cache, how to put files into the Cache, how to copy files, how to render files to the page, and how to dynamic create new files from data.
This is the same video as in module 1.1.
# Saving Files in Cache
When you want to save files in the cache to use later on, you first need to call the asynchronous method caches.open(cacheName). It returns a promise that will resolve to a cache object that
references the specific cache you opened. The cacheName is a String used as the reference name for a single cache. You can have as many caches as you want attached to your origin. They must each
have a unique name.
Following that, we call the cache.add() method, which will accept one of the following:
- a
Stringof a URL - a
URLobject - a
Requestobject
let cacheName = 'myCache-v1'; //key name for our cache
let assets = ['/img/demo.jpg', '/img/other.jpg', '/img/joanne.png'];
//an array of files to save in our cache
caches
.open(cacheName)
.then((cache) => {
let urlString = '/img/1011-800x600.jpg?id=one';
cache.add(urlString); //add = fetch + put
let url = new URL('http://127.0.0.1:5500/img/1011-800x600.jpg?id=two');
cache.add(url);
let req = new Request('/img/1011-800x600.jpg?id=three');
cache.add(req);
//cache.addAll(assets).then() is an alternative that lets you save a list
})
.catch((err) => {
//the open method failed.
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
In this example we are saving three copies of the same file (an image called 1011-800x600.jpg) into our cache. This could have been three different files. This code is just to illustrate that
changing the querystring value makes the browser view it as 3 different files.
If you want to have an array of file names you could call the cache.addAll method and pass in the array. The current domain name will be prepended to all those strings when the requests are made,
unless another protocol and domain are used at the start of the string.
The cache object also has fetch and put methods. The fetch method will download the file. The put method will save the file in the HTTP Response in the cache. The add method does both the
fetch and put steps together.
If you already have a Response object or a File object that can be wrapped in a Response object then you can use the put method instead of the add method. Remember that the cache.add(url)
will make a fetch call and save the response in the Cache. If your browser is offline then you will not be able to fetch the url. There will be times when you will already have your file to save in
the cache. In these cases you can use put.
let file = new File(['hello world'], 'sample.txt', { type: 'text/plain' });
let resp = new Response(file, {
status: 200,
statusText: 'Ok',
});
caches
.open(cacheName)
.then((cache) => {
cache.put('./fake/data/folder/sample.txt', resp);
//you can make up whatever path you want to use as the URL for your response.
})
.catch((err) => {
console.warn(err.message);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
If you ever need to remove something from the cache then you can call the cache.delete(reference) method to remove the file. It takes a Request, URL or USVString, which represent the file, as
an argument. It returns a Promise that resolves when the file has been deleted.
# Retrieving Files from Cache
When you want to read a file from the cache then we use the cache.match() method. It will return a Response object, just like a fetch call would. The difference is that it is looking at the
internal browser cache for your domain under the key used to create the cache object.
//the request argument can be a USVString, a URL object, or a Request object
//the options argument is optional. It is an object with 3 properties
let options = {
ignoreSearch: true, //ignore the queryString
ignoreMethod: true, //ignore the method - POST, GET, etc
ignoreVary: false, //ignore if the response has a VARY header
};
cache
.match(request, options)
.then((response) => {
// Do something with the response object... just like you would with fetch
// if(! response.ok) throw new Error(response.statusText)
// response.json()
// response.blob()
// response.text()
})
.then((obj) => {
//do something with the contents of the Response that was pulled from the Cache
})
.catch((err) => {
console.warn(err.message);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# List of Files in Cache
The Cache API also includes a keys() method which returns a Promise that will resolve to an Array of Response objects inside the selected cache.
caches
.open(cacheName)
.then((cache) => {
return cache.keys();
})
.then((keys) => {
//keys is the array of responses in the `cache`.
keys.forEach((request) => {
//output the url of the request
console.log(request.url);
});
})
.catch(console.warn);
2
3
4
5
6
7
8
9
10
11
12
13
If you want to retrieve a file you can always use the cache.match() method. If you want to retrieve multiple files then you can use the cache.matchAll() method.
# Delete Caches or Files
To delete a single file from the cache need a reference to the cache that contains the file and then, with the name of the file call the delete asynchronous method.
function deleteFile(filename) {
caches
.open(cacheName)
.then((cache) => {
return cache.delete(filename);
})
.then(() => {
//delete is complete
});
}
2
3
4
5
6
7
8
9
10
However, if you plan to delete multiple files from the cache then you need to wait for all the deletes to be successful. In these cases, Promise.all() is the approach you need. The Promise.all
method expects to be passed an array of promises. So, an array of calls to cache.delete() would be this.
//delete these three files from the cache "cacheName"
let filesToDelete = ['one.jpg', 'two.html', 'three.css'];
caches
.open('cacheName')
.then((cache) => {
return Promise.all(
filesToDelete.forEach((filename) => {
cache.delete(filename);
})
);
})
.then(() => {
//successfully deleted all those files
})
.catch(console.warn);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NOTE: If
caches.keys()is called, on thecachesobject, then the Array contains a list of the cache names. Ifcache.keys()is called then the Array will contain a list of theRequestobjects in that specific cache.
If you are looking to delete one or more caches, not just individual files, then you can use the same approach with the Promise.all() around the call to caches.keys() and then a filter().map()
to delete multiple caches. In the following example, the variables cache1 and cache2 will represent the names of two different caches that might exist on the user's computer.
//to delete multiple caches except the ones
// in cache1 and cache2
Promises.all(
caches.keys().then((keys) => {
return keys.filter((nm) => nm !== cache1 && nm !== cache2).map((nm) => caches.delete(nm));
})
);
2
3
4
5
6
7
# ToDo This Week
To Do this week
- read all the content for modules 2.1 and 2.2
- Complete and submit Hybrid exercise 1 - JS Class Quiz.
- Review the description for the Cache API Assignment.