iT邦幫忙

2025 iThome 鐵人賽

DAY 25
3
Modern Web

從 Canvas 到各式各樣的 Web API 之旅系列 第 25

Day 25 - Service Worker 快取策略與更新陷阱

  • 分享至 

  • xImage
  •  

昨天我們介紹了 Service Worker 的核心特性、生命週期、Cache API。今天要往前一步,來看看 快取策略,以及開發時很容易踩到的 更新陷阱


圖解複習:Service Worker 的攔截與快取原理

在討論「為什麼要有快取策略?」之前,先用三張圖快速複習 SW 的工作方式,幫你把觀念釐清:

  • 當瀏覽器發送請求,Service Worker 會先攔截,決定要「直送伺服器」「回傳快取」「阻擋」。
  • 取得網路回應後,SW 可以把回應複製一份存到 Cache,下次同樣的請求就能更快命中。
  • 即使網路中斷,SW 仍能回應快取中的資源,讓頁面具備基本的離線能力。

這些是策略的共同基礎,接下來的快取策略,只是「在攔截時怎麼選擇路徑」的不同。

圖示:當瀏覽器發送請求時,Service Worker 一定會攔截。它可以選擇直接放行到 Server,也能攔截後改成回傳快取,甚至完全阻擋請求。
Service Worker

圖示:Service Worker 在請求網路資源時,除了把資料回給瀏覽器,也會把回應複製一份存到快取,下次就能更快命中。
Service Worker

圖示:網路中斷後,請求無法送到伺服器,Service Worker 仍能回應快取中的資源,讓網站在離線狀態下依舊可用。
Service Worker


為什麼要有快取策略?

在 Service Worker 的 fetch 事件裡,我們可以決定「要回快取,還是要呼叫網路」。但不同應用場景需求不同:

  • 新聞網站:希望顯示最新內容 → 「Network First」比較合理。
  • 靜態資源(JS/CSS/圖片):更新頻率低,用快取更快 → 「Cache First」。
  • 產品清單:希望使用者馬上看到結果,但背景能更新資料 → 「Stale-While-Revalidate」。

這些就是常見的三種快取策略。它們沒有絕對好壞,而是要根據需求選擇。


策略一:Cache First

原理

優先讀取快取,如果快取裡沒有,才去呼叫網路。常用於圖片、字型、版本固定的 JS/CSS 等資源。

程式碼範例

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request);
    })
  );
});

優點:速度快,體驗好。
缺點:可能回傳舊資料,除非重新部署 SW。


策略二:Network First

原理

優先打網路,如果網路失敗(例如離線),才回快取。常用於動態內容:API、新聞、即時資料。

程式碼範例

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then(res => {
        // 成功回應就順便更新快取
        const resClone = res.clone();
        caches.open('dynamic-cache').then(cache => cache.put(event.request, resClone));
        return res;
      })
      .catch(() => caches.match(event.request))
  );
});

優點:確保資料最新。
缺點:若網路慢,使用者會等很久;若網路斷,才會退回快取。


策略三:Stale-While-Revalidate

原理

先回快取(讓使用者秒開),同時在背景打網路,等新資料回來再更新快取。下次開啟就能看到更新後的結果。

程式碼範例

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      const fetchPromise = fetch(event.request).then(res => {
        const resClone = res.clone();
        caches.open('dynamic-cache').then(cache => cache.put(event.request, resClone));
        return res;
      });
      return cached || fetchPromise;
    })
  );
});

優點:快取命中時使用者秒開,同時能在背景更新。
缺點:使用者可能短暫看到舊資料(需要 UI 設計上標示)。


更新陷阱:為什麼我的 SW 沒有馬上生效?

昨天 Day 24 - 從頭瞭解 Service Worker 已經提過 SW 的「更新陷阱」。這是最常讓人困惑的地方,這裡再用更直白的方式說一次:

我改了 sw.js,重新整理網頁,但看到的還是舊版本!🤔

原因在於:新版本要等舊頁面都關閉,才能 activate。

  1. 新的 SW 下載並 install 完成後,不會立刻生效,而是先進入 waiting 狀態。
  2. 只要還有分頁在使用舊版 SW,新的 SW 就必須繼續等待。
  3. 當舊版 SW 不再有任何 client(分頁)在使用時,新的 SW 才會 activate,正式接手控制。

這樣的設計是為了保證:同一個頁面全程只被一個 SW 控制,避免中途換控造成:

  • 目前的 fetch 請求可能被打斷
  • cache 狀態可能不一致
  • 同一個網站的兩個分頁行為不同步

可以「立刻啟用」嗎?

可以,但有限制:

  • self.skipWaiting():跳過 waiting,直接進入 activate,即使還有舊分頁存在。可能導致不同分頁狀態不一致。
  • clients.claim():讓已啟用的新 SW 立即接管 「尚未被任何 SW 控制」 的分頁(例如第一次載入的頁面)。但已被舊 SW 控制的分頁,仍需重新整理才能切換成新 SW。

開發時的解法

  • 在 DevTools > Application > Service Worker 勾選 Update on reload,每次刷新都強制更新。
  • 或在程式中呼叫 skipWaiting()clients.claim(),不過要小心可能引發不同分頁狀態不一致的情況。

Service Worker

Service Worker


小結

今天我們認識了三種常見的快取策略:

  • Cache First:適合靜態資源。
  • Network First:適合即時資料。
  • Stale-While-Revalidate:兼顧體驗與更新。

並且補充了 更新陷阱:為什麼新版本 SW 沒有馬上生效,以及該如何處理。


👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯


上一篇
Day 24 - 從頭瞭解 Service Worker
系列文
從 Canvas 到各式各樣的 Web API 之旅25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言