Strategi Cache then Network

Pada Strategi Cache then Network, page langsung mengakses cache, pada saat bersamaan service worker juga melakukan request ke network untuk mendapatkan konten terbaru dan melakukan update ke dalam cache dan menampilkan konten terbaru pada user.

Untuk file finished project dapat di download disini. File ini juga akan digunakan sebagai startup project pada course PWA Lanjutan.

Cache then Network
  • Kelebihan dari pendekatan ini adalah web app dengan cepat memberikan respon, dan cache akan selalu diperbaharui dengan konten terbaru.
  • Kekurangannya adalah cukup rumit dalam coding, karena akses cache sekarang dilakukan dari file javascript biasa dan file javascirpt service worker.

Catatan File Project

Pada project ini digunakan simulasi network request ke httpbin.org. Simulasi request ini hanya bertujuan mentrigger fetch event yang akan mengupdate isi cache dengan fungsi createCard.

Bertujuan untuk menunjukan saat offline (dimana simulasi network request tidak bisa dilakukan), web app akan membaca dari cache. (lihat file feed.js)

Note: Digunakan httpbin.org karena untuk mempermudah proses tutorial, kita tidak perlu mensetup server untuk simulasi network request. Pada course PWA Lanjutan, network request ke httpbin.org akan diganti dengan real server di Firebase.

//file feed.js
var sharedMomentsArea = document.querySelector('#shared-moments');
var url = 'https://httpbin.org/get';
var networkDataReceived = false;

function clearCards() {
  while(sharedMomentsArea.hasChildNodes()) {
    sharedMomentsArea.removeChild(sharedMomentsArea.lastChild);
  }
}

function createCard() {
  var cardWrapper = document.createElement('div');
  cardWrapper.className = 'shared-moment-card mdl-card mdl-shadow--2dp';
  cardWrapper.style = "display:inline-block;";
  var cardTitle = document.createElement('div');
  cardTitle.className = 'mdl-card__title';
  cardTitle.style.backgroundImage = 'url("/src/images/kuta-seminyak.jpg")';
  cardTitle.style.backgroundSize = 'cover';
  cardTitle.style.height = '180px';
  cardWrapper.appendChild(cardTitle);
  var cardTitleTextElement = document.createElement('h2');
  cardTitleTextElement.style.color = 'white';
  cardTitleTextElement.className = 'mdl-card__title-text';
  cardTitleTextElement.textContent = 'Bali Trip';
  cardTitle.appendChild(cardTitleTextElement);
  var cardSupportingText = document.createElement('div');
  cardSupportingText.className = 'mdl-card__supporting-text';
  cardSupportingText.textContent = 'Seminyak Beach At Night';
  cardSupportingText.style.textAlign = 'center';
  // var cardSaveButton = document.createElement('button');
  // cardSaveButton.textContent = 'Save';
  // cardSaveButton.addEventListener('click', onSaveButtonClicked);
  // cardSupportingText.appendChild(cardSaveButton);
  cardWrapper.appendChild(cardSupportingText);
  componentHandler.upgradeElement(cardWrapper);
  sharedMomentsArea.appendChild(cardWrapper);
}

fetch(url)
  .then(function(res) {
    return res.json();
  })
  .then(function(data) {
    networkDataReceived = true;
    console.log('From web', data);
    clearCards();
    createCard();
  });

if ('caches' in window) {
  caches.match(url)
    .then(function(response) {
      if (response) {
        return response.json();
      }
    })
    .then(function(data) {
      console.log('From cache', data);
      if (!networkDataReceived) {
        clearCards();
        createCard();
      }
    });
}

Perubahan code juga terjadi pada file sw.js. Karena menggunakan strategi Cache then Network, perubahan code event fetch diubah total. Selain itu kita juga melakukan tuning untuk manajemen cache dan fallback.

//file sw.js

var APPSHELL_CACHE = 'appshell-v6';
var DYNAMIC_CACHE = 'dynamic';
var STATIC_FILES = [
    '/',
    '/index.html',
    '/offlinepage.html',
    '/src/js/app.js',
    '/src/js/feed.js',
    '/src/js/material.min.js',
    '/src/css/app.css',
    '/src/css/feed.css',
    '/src/images/main-image.jpg',
    'https://fonts.googleapis.com/css?family=Roboto:400,700',
    'https://fonts.googleapis.com/icon?family=Material+Icons',
    'https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.indigo-pink.min.css'
  ];
//service worker life cycle event related

self.addEventListener('install', function(event){
    console.log('[SW] Installing service worker...', event);
    event.waitUntil(
        caches.open(APPSHELL_CACHE)
        .then(function(cache){
            console.log('[SW] Pre-caching app shell..');
            cache.addAll(STATIC_FILES);
        })
    );
});

self.addEventListener('activate', function(event){
    console.log('[SW] activating service worker...', event);
    event.waitUntil(
        caches.keys()
        .then(function(keyList){
            return Promise.all(keyList.map(function(key){
                if (key !== APPSHELL_CACHE && key !== DYNAMIC_CACHE){
                    return caches.delete(key);
                }
            }));
        })
    )
    return self.clients.claim();
});

function isInArray(string, array) {
    var cachePath;
    if (string.indexOf(self.origin) === 0) { // request targets domain where we serve the page from (i.e. NOT a CDN)
      console.log('matched ', string);
      cachePath = string.substring(self.origin.length); // take the part of the URL AFTER the domain (e.g. after localhost:8080)
    } else {
      cachePath = string; // store the full request (for CDNs)
    }
    return array.indexOf(cachePath) > -1;
  }
  
self.addEventListener('fetch', function (event) {
  
    var url = 'https://httpbin.org/get';
    if (event.request.url.indexOf(url) > -1) {
        event.respondWith(
        caches.open(DYNAMIC_CACHE)
            .then(function (cache) {
            return fetch(event.request)
                .then(function (res) {
                cache.put(event.request, res.clone());
                return res;
                });
            })
        );
    } else if (isInArray(event.request.url, STATIC_FILES)) {
        event.respondWith(
        caches.match(event.request)
        );
    } else {
        event.respondWith(
        caches.match(event.request)
            .then(function (response) {
            if (response) {
                return response;
            } else {
                return fetch(event.request)
                .then(function (res) {
                    return caches.open(DYNAMIC_CACHE)
                    .then(function (cache) {
                        cache.put(event.request.url, res.clone());
                        return res;
                    })
                })
                .catch(function (err) {
                    return caches.open(DYNAMIC_CACHE)
                    .then(function (cache) {
                        if (event.request.headers.get('accept').includes('text/html')) {
                            return cache.match('/offlinepage.html');
                        }
                    });
                });
            }
            })
        );
    }
});

Kesimpulan

Setiap strategi ada keunggulan dan kelemahannya. Silakan bereksperimen, gunakan strategi tertentu untuk kasus-kasus tertentu agar hasilnya maksimal. Semoga bermanfaat.

Untuk fitur PWA lainnya seperti IndexDB, Background Synch, Web Push Notification dan Native Device Acces akan dibahas di course PWA Advanced.

Sharing is caring: