這個策略算是目前應用場景最為廣泛的。主要目的就是能「從cache中盡快的獲得用戶所需要的資源」,並且「同時也透過網路去fetch該資源」,若fetch回來的資源為較新的版本,則取代從cache中獲得的資源來傳給用戶。(可以把它想成是Network with Cache Fallback Strategies的進階版。因為當網路不穩定時,用戶不在需要等待過長的時間)
來看一下在我的PWA project要怎麼實現這個策略吧:(首先這邊不是在service woker中撰寫,而是要在feed.js中來存取cache)
var url = 'https://httpbin.org/get';  // 我們就是要從該外部server的API獲取資源
var networkDataReceived = false;
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();
    }
  });
}
function clearCards() {
  while(sharedMomentsArea.hasChildNodes()) {
    sharedMomentsArea.removeChild(sharedMomentsArea.lastChild);
  }
}
這邊說明一下我的程式邏輯,一開始先用fetch API來獲取外部server的資源,並同時確認caches是否在window object中,如果瀏覽器有支援就到cache中尋找是否有相對應已經暫存的資源,有的話就回傳。
這邊為了避免我同時成功地從「cache」和「網路上」獲取該資源,重複執行了createCards(),我使用networkDataReceived這個布林變數來去做判斷。當fetch API先回傳我們要的資源,我們只需要針對這個回傳的資料來建立我們的card(貼文)。就算cache裡存在我們要的資源也不必再建立一個card。
再來就是我新增了一個clearCards() function,並加在createCards()前。這樣每次載入我的PWA時,都會全部重新刪掉這些cards,避免累加重複的cards越來越多,之後再將fetch回來的資源重新建立cards。
眼尖的人可能會發現,前面流程圖的第4和5步驟還沒有寫RR(也就是在service worker中針對fetch回來的資源使用dynamic caching)。那我們接著就來看一下要怎麼在sw.js file中加入這段邏輯吧!! ![]()
// Cache then network
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.open(CACHE_DYNAMIC_NAME).then(function(cache) {
            return fetch(event.request).then(function(res) {
                cache.put(event.request, res.clone());
                return res;
            });
        })
    );
});
一樣地在service worker監聽fetch event時,將透過網路fetch回來的response,使用cache.put()添加到dynamic cache中,並回傳該response。
不過,當offline時畫面結果卻是....Why??
仔細看一下我剛在service worker中寫的那段code,其實我並沒有寫service worker要到cache中獲取資源的程式碼QQ,我只是put進去後就回傳了。
也就是說我們所有的fetch request被service worker攔截後,如果今天在沒有網路連線的情形下,它去fetch所有不管是「App shell中的靜態資源」還是「動態資源」都會失敗。
不過這也正是cache than network的使用場景,它會在網路正常連線時,快速的從cache中獲取該資源給用戶。但這個策略流程並不能在沒有網路連線時,從cache中獲取資源。
為了在離線時也能正常運作,這邊要再修改剛剛寫的code。我要將「cache then network strategies」和之前所寫的「cache with network fallback strategies」根據不同的request來使用不同的策略:
// Cache then Network
self.addEventListener('fetch', function(event) {
    var url = 'https://httpbin.org/get';
    if(event.request.url.indexOf(url) > -1) {
        event.respondWith(
            caches.open(CACHE_DYNAMIC_NAME).then(function(cache) {
                return fetch(event.request).then(function(res) {
                    cache.put(event.request, res.clone());
                    return res;
                });
            })
        );
    } 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(CACHE_DYNAMIC_NAME).then(function(cache) {
                            cache.put(event.request.url, res.clone());
                            return res;
                        })
                    }).catch(function(err) {
                        return caches.open(CACHE_STATIC_NAME).then(function(cache) {
                            return cache.match('/offline.html');
                        });
                    });
                }
            })
        );
    }
});
這邊我將「要去fetch外部server資料的request」才使用「cache then network strategies」,而其他的request則是使用我們之前所寫的「cache with network fallback strategies」。這樣的話當offline時,在cache中的App shell靜態資源或是其他動態資源就都可以回傳了。
Day12 結束!! ![]()