Showing cached content first then networks

8 min read

In this article by Sean Amarasinghe, the author of the book, Service Worker Development Cookbook, we are going to look at the methods that enable us to control cached content by creating a performance art event viewer web app. If you are a regular visitor to a certain website, chances are that you may be loading most of the resources, like CSS and JavaScript files, from your cache, rather than from the server itself. This saves us necessary bandwidth for the server, as well as requests over the network. Having the control over which content we deliver from the cache and server is a great advantage. Server workers provide us with this powerful feature by having programmatic control over the content.

(For more resources related to this topic, see here.)

Getting ready

To get started with service workers, you will need to have the service worker experiment feature turned on in your browser settings. Service workers only run across HTTPS.

How to do it…

Follow these instructions to set up your file structure. Alternatively, you can download the files from the following location:

https://github.com/szaranger/szaranger.github.io/tree/master/service-workers/03/02/

  1. First, we must create an index.html file as follows:
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Cache First, then Network</title>
      <link rel="stylesheet" href="style.css">
    </head>
    <body>
      <section id="events">
        <h1><span class="nyc">NYC</span> Events TONIGHT</h1>
        <aside>
          <img src="hypecal.png" />
          <h2>Source</h2>
          <section>
            <h3>Network</h3>
            <input type="checkbox" name="network" id="network-
            disabled-checkbox">
            <label for="network">Disabled</label><br />
            <h3>Cache</h3>
            <input type="checkbox" name="cache" id="cache-
            disabled-checkbox">
            <label for="cache">Disabled</label><br />
          </section>
          <h2>Delay</h2>
          <section>
            <h3>Network</h3>
            <input type="text" name="network-delay" 
            id="network-delay" value="400" /> ms
            <h3>Cache</h3>
            <input type="text" name="cache-delay" id="cache-
            delay" value="1000" /> ms
          </section>
        <input type="button" id="fetch-btn" value="FETCH" />
      </aside>
      <section class="data connection">
        <table>
          <tr>
            <td><strong>Network</strong></td>
            <td><output id='network-status'></output></td>
          </tr>
          <tr>
            <td><strong>Cache</strong></td>
            <td><output id='cache-status'></output><td>
          </tr>
        </table>
      </section>
      <section class="data detail">
        <output id="data"></output>
      </section>
      <script src="index.js"></script>
    </body>
    </html>
  2. Create a CSS file called style.css in the same folder as the index.html file. You can find the source code in the following location on GitHub:

    https://github.com/szaranger/szaranger.github.io/blob/master/service-workers/03/02/style.css

  3. Create a JavaScript file called index.js in the same folder as the index.html file. You can find the source code in the following location on GitHub:

    https://github.com/szaranger/szaranger.github.io/blob/master/service-workers/03/02/index.js

  4. Open up a browser and go to index.html.
  5. First we are requesting data from the network with the cache enabled. Click on the Fetch button.
  6. If you click fetch again, the data has been retrieved first from cache, and then from the network, so you see duplicate data. (See the last line is same as the first.)
  7. Now we are going to select the Disabled checkbox under the Network label, and click the Fetch button again, in order to fetch data only from the cache.
  8. Select the Disabled checkbox under the Network label, as well as the Cache label, and click the Fetch button again.

How it works…

In the index.js file, we are setting a page specific name for the cache, as the caches are per origin based, and no other page should use the same cache name:

var CACHE_NAME = 'cache-and-then-network';

If you inspect the Resources tab of the development tools, you will find the cache inside the Cache Storage tab.

If we have already fetched network data, we don’t want the cache fetch to complete and overwrite the data that we just got from the network. We use the networkDataReceived flag to let the cache fetch callbacks to know if a network fetch has already completed:

var networkDataReceived = false;

We are storing elapsed time for both network and cache in two variables:

var networkFetchStartTime;
var cacheFetchStartTime;

The source URL for example is pointing to a file location in GitHub via RawGit:

var SOURCE_URL = 'https://cdn.rawgit.com/szaranger/
szaranger.github.io/master/service-workers/03/02/events';

If you want to set up your own source URL, you can easily do so by creating a gist, or a repository, in GitHub, and creating a file with your data in JSON format (you don’t need the .json extension). Once you’ve done that, copy the URL of the file, head over to https://rawgit.com, and paste the link there to obtain another link with content type header as shown in the following screenshot:

Between the time we press the Fetch button, and the completion of receiving data, we have to make sure the user doesn’t change the criteria for search, or press the Fetch button again. To handle this situation, we disable the controls:

function clear() {
  outlet.textContent = '';
  cacheStatus.textContent = '';
  networkStatus.textContent = '';
  networkDataReceived = false;
}

function disableEdit(enable) {
  fetchButton.disabled = enable;
  cacheDelayText.disabled = enable;
  cacheDisabledCheckbox.disabled = enable;
  networkDelayText.disabled = enable;
  networkDisabledCheckbox.disabled = enable;

  if(!enable) {
    clear();
  }
}

The returned data will be rendered to the screen in rows:

function displayEvents(events) {

  events.forEach(function(event) {
    var tickets = event.ticket ?
      '<a href="' + event.ticket + '" class="tickets">Tickets</a>' 
      : '';

    outlet.innerHTML = outlet.innerHTML +
      '<article>' +
      '<span class="date">' + formatDate(event.date)  + '</span>' 
      +
      ' <span class="title">' + event.title + '</span>' +
      ' <span class="venue"> - ' + event.venue + '</span> ' +
      tickets +
      '</article>';
  });

}

Each item of the events array will be printed to the screen as rows.

The function handleFetchComplete is the callback for both the cache and the network.

If the disabled checkbox is checked, we are simulating a network error by throwing an error:

var shouldNetworkError = networkDisabledCheckbox.checked,
    cloned;

  if (shouldNetworkError) {
    throw new Error('Network error');
  }

Because of the reason that request bodies can only be read once, we have to clone the response:

cloned = response.clone();

We place the cloned response in the cache using cache.put as a key value pair. This helps subsequent cache fetches to find this update data:

caches.open(CACHE_NAME).then(function(cache) {
   cache.put(SOURCE_URL, cloned); // cache.put(URL, response)
});

Now we read the response in JSON format. Also, we make sure that any in-flight cache requests will not be overwritten by the data we have just received, using the networkDataReceived flag:

response.json().then(function(data) {
    displayEvents(data);
    networkDataReceived = true;
  });

To prevent overwriting the data we received from the network, we make sure only to update the page in case the network request has not yet returned:

result.json().then(function(data) {
    if (!networkDataReceived) {
      displayEvents(data);
    }
  });

When the user presses the fetch button, they make nearly simultaneous requests of the network and the cache for data. This happens on a page load in a real world application, instead of being the result of a user action:

fetchButton.addEventListener('click', function handleClick() {
...
}

We start by disabling any user input while the network fetch requests are initiated:

disableEdit(true);

networkStatus.textContent = 'Fetching events...';
networkFetchStartTime = Date.now();

We request data with the fetch API, with a cache busting URL, as well as a no-cache option in order to support Firefox, which hasn’t implemented the caching options yet:

networkFetch = fetch(SOURCE_URL + '?cacheBuster=' + now, {
   mode: 'cors',
   cache: 'no-cache',
   headers: headers
})

In order to simulate network delays, we wait before calling the network fetch callback. In situations where the callback errors out, we have to make sure that we reject the promise we received from the original fetch:

return new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          handleFetchComplete(response);
          resolve();
        } catch (err) {
          reject(err);
        }
      }, networkDelay);
    });

To simulate cache delays, we wait before calling the cache fetch callback. If the callback errors out, we make sure that we reject the promise we got from the original call to match:

return new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            handleCacheFetchComplete(response);
            resolve();
          } catch (err) {
            reject(err);
          }
        }, cacheDelay);
      });

The formatDate function is a helper function for us to convert the date format we receive in the response into a much more readable format on the screen:

function formatDate(date) {
  var d = new Date(date),
      month = (d.getMonth() + 1).toString(),
      day = d.getDate().toString(),
      year = d.getFullYear();

  if (month.length < 2) month = '0' + month;
  if (day.length < 2) day = '0' + day;

  return [month, day, year].join('-');
}

If you consider a different date format, you can shuffle the position of the array in the return statement to your preferred format.

Summary

In this article, we have learned how to control cached content by creating a performance art event viewer web app.

Resources for Article:


Further resources on this subject:


Packt

Share
Published by
Packt

Recent Posts

Harnessing Tech for Good to Drive Environmental Impact

At Packt, we are always on the lookout for innovative startups that are not only…

2 months ago

Top life hacks for prepping for your IT certification exam

I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…

3 years ago

Learn Transformers for Natural Language Processing with Denis Rothman

Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…

3 years ago

Learning Essential Linux Commands for Navigating the Shell Effectively

Once we learn how to deploy an Ubuntu server, how to manage users, and how…

3 years ago

Clean Coding in Python with Mariano Anaya

Key-takeaways:   Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…

3 years ago

Exploring Forms in Angular – types, benefits and differences   

While developing a web application, or setting dynamic pages and meta tags we need to deal with…

3 years ago