Service Worker 快取的策略

目前,Cache 介面只有提及和簡短程式碼片段。如要有效使用服務工作站,您必須採用一或多項快取策略,這麼做需要稍微熟悉 Cache 介面。

快取策略是服務工作站 fetch 事件與 Cache 介面之間的互動。快取策略的編寫方式視實際情況而定;舉例來說,處理靜態資產的要求可能與文件不同,而且這會影響快取策略的組成方式。

在深入探討這些策略前,我們先來談談 Cache 介面有哪些功能、其作用,並快速介紹其中提供的一些方法,以管理 Service Worker 快取。

Cache 介面與 HTTP 快取

如果您之前未曾使用 Cache 介面,您可能會想將其視為與 HTTP 快取相同,或至少與 HTTP 快取相關。但事實並非如此。

  • Cache 介面是與 HTTP 快取完全不同的快取機制。
  • 無論使用何種 Cache-Control 設定影響 HTTP 快取,都不會影響 Cache 介面中儲存的資產。

不妨將瀏覽器快取視為分層。 HTTP 快取是由鍵/值組合驅動的低階快取,並且會在 HTTP 標頭中表示的指令。

相反地,Cache 介面是由 JavaScript API 驅動的高階快取。比起使用相對簡單的 HTTP 鍵/值組合時,這種方式更有彈性,也是快取策略可行的一半。Service Worker 快取的一些重要 API 方法如下:

以上僅列舉部分例子,還有其他實用的方法,不過您會在本指南中稍後會使用這些基本方法。

簡單的 fetch 事件

快取策略的其餘部分是 Service Worker 的 fetch 事件。到目前為止,您在這份說明文件中已聽過「攔截網路要求」,而 Service Worker 中的 fetch 事件則發生於這個情況:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

這是玩具範例,為自己查看的實際運作情形,但這只是讓您概略瞭解服務工作站的功能。上述程式碼會執行以下操作:

  1. 檢查要求的 destination 屬性,看看這是否為圖片要求。
  2. 如果圖片位於 Service Worker 快取中,請從該處提供。 如果沒有,請從網路擷取圖片,將回應儲存在快取中,然後傳回網路回應。
  3. 所有其他要求都會透過 Service Worker 傳送,且不需與快取互動。

擷取的 event 物件包含 request 屬性,其中包含一些實用資訊,可協助您識別每項要求的類型:

  • url,這是 fetch 事件目前處理的網路要求網址。
  • method,是要求方法 (例如GETPOST)。
  • mode,用於說明要求的模式。'navigate' 值通常用於區分 HTML 文件要求和其他要求。
  • destination,用來描述要求的內容類型,避免使用要求的素材資源副檔名。

再一次, asynchrony 是遊戲的名稱。 您將會知道,install 事件提供具有承諾的 event.waitUntil 方法,並等待問題解決後,才會繼續啟用程序。fetch 事件提供類似的 event.respondWith 方法,可用來傳回非同步 fetch 要求的結果,或 Cache 介面的 match 方法傳回的回應。

快取策略

現在您已稍微瞭解 Cache 執行個體和 fetch 事件處理常式,接著可以開始深入瞭解部分服務工作站的快取策略。儘管具有無限可能性,但本指南將持續納入 Workbox 隨附的策略,讓您瞭解 Workbox 內部的具體內容。

僅快取

顯示從頁面流向 Service Worker 到快取的流程。

讓我們先從簡單的快取策略開始,稱為「僅限快取」。 這是因為服務工作站在網頁受到控制時,比對要求只會前往快取。這表示任何快取的資產都必須預先快取,模式才能正常運作,而且在 Service Worker 更新之前,這些資產都不會在快取中更新。

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

上面,系統會在安裝時預先快取一系列資產。 服務工作站處理擷取作業時,系統會檢查 fetch 事件處理的要求網址是否位於預先快取資產的陣列中。如果已啟用,我們就會從快取中擷取資源,然後略過網路。 其他要求只會傳遞至網路,且只有網路。如要查看這項策略的應用實例,請在控制台開啟時觀看此示範影片

僅限網路

顯示從頁面到 Service Worker 的流向網路。

「僅限快取」則相反,也就是在不與 Service Worker 快取任何互動的情況下,透過服務工作站將要求傳遞至網路。這是確保內容更新間隔 (例如標記) 是很好的策略,但權衡在於當使用者離線時一律不會運作。

確保要求能傳遞至網路,只是您不會針對相符的要求呼叫 event.respondWith。如果需要明確的 return;,您可以針對要傳遞至網路的要求,在 fetch 事件回呼中合併空白的 return;。請參考「僅快取」策略示範,瞭解沒有預載要求時會發生的情況。

先快取,回到網路

顯示從頁面流向 Service Worker 到快取的作業,如果不在快取中,再顯示連至網路的流程。

這項策略就是讓事情更加複雜。針對比對要求,其程序如下:

  1. 這項要求會到達快取。如果資產位於快取中,請從該處提供。
  2. 如果要求「未」在快取中,請前往該網路。
  3. 網路要求完成後,將其新增至快取,然後從網路傳回回應。

以下是這項策略的範例,您可以使用現場示範進行測試:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

雖然這個例子僅涵蓋圖片,但這個策略仍適用於所有靜態資產 (例如 CSS、JavaScript、圖片和字型),特別是經過雜湊處理的版本。這項功能可以一邊執行任何內容更新檢查,向伺服器執行 HTTP 快取可能會啟動的內容更新檢查,為不可變更的資產加快速度。更重要的是,任何快取的資產都能離線使用。

網路優先,返回快取

顯示從網頁流向 Service Worker 和網路流向,並在沒有網路的情況下進入快取狀態。

如果您原本要翻轉「快取優先,網路秒」,最後就會有「網路優先,快取秒」策略,看起來會像這樣:

  1. 您會先前往網路的要求,並將回應放到快取中。
  2. 如果您稍後處於離線狀態,您將在快取中改回使用最新版本的回應。

當您在線上時,想要取得最新版的資源,但又想要離線存取最新的可用版本時,這項策略就非常適合採用此策略。申請 HTML 時可能會如下所示:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

您可以透過示範模式試用這項功能。首先,請前往頁面。您可能需要先重新載入,才能將 HTML 回應存入快取。 然後在開發人員工具中模擬離線連線,然後重新重新載入。系統會立即透過快取提供最後一個可用的版本。

如果離線功能很重要,但您需要在功能與最新版標記或 API 資料的存取權之間取得平衡時,「網路優先,快取秒」是達成該目標的穩固策略。

過時的重新驗證

顯示從頁面流向服務工作處理程序、到快取的流程,以及從網路到快取的流程。

在我們目前介紹的策略中,「過時-階段重新驗證」是最複雜的。 這在某些方面與最後兩項策略類似,但此程序會優先處理資源存取速度,同時在背景中保持最新狀態。這項策略的運作方式如下:

  1. 針對資產的第一個要求時,從網路擷取該資產,並將其置於快取中,然後傳回網路回應。
  2. 在後續要求中,請先從快取提供資產,然後「在背景」重新向網路要求資產,並更新資產的快取項目。
  3. 在這之後的要求,您會收到從上一個步驟中快取的網路擷取的最後一個版本。

這種做法對需要跟上最新異動的重要項目來說是很好的策略,但並不重要。不妨將事物視為社群媒體網站的顯示圖片。 當使用者更新應用程式時,系統則會更新這類要求,但並非每次要求都必須使用最新版本。

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

您可以透過另一個實際示範查看實際運作情形,特別是當您留意瀏覽器的開發人員工具和其 CacheStorage 檢視器 (如果瀏覽器的開發人員工具有提供) 的網路分頁時。

前往工作箱!

本文件總結了我們對 Service Worker 的 API 和相關 API 的看法,這代表您已瞭解如何直接使用服務工作站進行 Workbox 的調整!