If you want to learn everything you need to know about service workers, head over to serviceworkerbook.com and pre-order my book, “Let’s Take This Offline”!

What is cache.add?

If you’ve ever looked at a service worker, you’re probably familiar with this little bit of code from an oninstall function:

cache.addAll([ /* ... */ ]);

Or perhaps you’ve seen:

cache.add(/* ... */);

Fortunately the syntax is pretty self-explanatory on what these two functions are doing. They are adding items to a cache. In one case they add multiple files, and in another, only a single file or request.

What is cache.put

Another function for adding items to a cache is cache.put. This function has two parameters: the request and the response.

cache.put(request, response);

So besides the obvious difference, that addAll and add only accept a single parameter (though of varying types), what is the difference between add and put?

The difference between add and put

It’s a trick question! As it turns out, add and addAll use put under the hood.

It works like this:

    1. For each request in the array (or a single request in the case of `add`), do a fetch
    2. Add the response to a key-value pair list, with the request as the identifying key
    3. Cache the request _and_ response together (by using put)

Source: Service Worker Spec

When to use add and when to use put

Usually in a service worker install function, you’ll see this:

// code: https://github.com/carmalou/cache-response-with-sw/blob/master/serviceworker.js

self.oninstall = function() {
    caches.open(cacheName).then(function(cache) {
        cache.addAll([ /* ... */ ])
        .then(/* ... */)
        .catch(/* ... */)
    })
}

It makes a lot of sense to use addAll over put in this case, because the user has never visited our site before. addAll is going to go fetch those responses and add them to your cache automatically, so we can use it during once our install event and no longer need to go over the network to get our static assets like HTML files. Additionally the syntax is very clean, so its very clear to future-us what we’re doing.

So, if we should use addAll in an install event, where should we use put?

Before we get into the add vs put debate, let’s look at our fetch event.

self.onfetch = function(event) {
    event.respondWith(
        caches.match(event.request)
        .then(function(cachedFiles) {
            if(cachedFiles) {
                return cachedFiles;
            } else {
                // should I use cache.put or cache.add??
            }
          })
        )
    )
}
        

In the above code, we have a fetch event sitting between the client making the request and the network which will process the request. Our service worker will intercept our request and check for a match within our cache. If a match is found, the service worker will return the response. Otherwise, we’ll fall into the else.

Let’s first look at using cache.add:

/* ... */

if(cachedFiles) {
    return cachedFiles;
} else {
    return caches.open(cacheName)
    .then(function(cache) {
        return caches.add(request)
        .then(function() {
            return fetch(request)
        });
    })
}

/* ... */

Briefly, should no match be found, we’ll fall into the else. Within the else, we’ll go ahead and open up the cache we want to store our data in, and then use cache.add to store off the response. At the end, we’ll go ahead and do a fetch over the network so the client can access the data.

But there’s a problem with this: we’ll end up needing to do two fetches! Because caches.add doesn’t actually return the response to the request it runs, we still need to do an additional fetch to get the response back to the client. Doing two fetches for the same data is redundant, and fortunately put makes it unnecessary.

Let’s take a look at how we might rewrite this with put:

// code: https://github.com/carmalou/cache-response-with-sw/blob/master/serviceworker.js

/* ... */

if(cachedFiles) {
    return cachedFiles;
} else {
    return fetch(event.request)
    .then(function(response) {
        return caches.open(cacheName)
        .then(function(cache) {
            return cache.put(event.request, response.clone());
        })
        .then(function() {
            return response;
        })
    })
}

/* ... */

So we’ve sort of flipped the order of what we were doing before with cache.add. Instead of doing the fetch last, we go ahead and do it first. After that’s completed, we have a reference to the response. Next we can go ahead and get a reference to our cache with caches.open and use put to cache both the request and the response.

Note: Keep your eyes peeled for a blog post about why we are cloning the response!

Once we’ve finished caching the data, we can go ahead and return the response back to the client! And then next time the client makes this request, a trip over the network will no longer be necessary!

So, in conclusion, you should use cache.add or cache.addAll in situations where you don’t need that data to get back to the client – such as your install event. When you’re caching new requests, such as API requests, it’s better to use cache.put because it allows you to cache the data and send it back to the client with a single network request.

If you want to learn more about service workers, I hope you’ll head over to serviceworkerbook.com and sign up for my mailing list, and follow me on Twitter! You’ll be the first to know when my book, ‘Let’s Take This Offline’ is out!