昨天我們介紹了 Service Worker 的核心特性、生命週期、Cache API。今天要往前一步,來看看 快取策略,以及開發時很容易踩到的 更新陷阱。
在討論「為什麼要有快取策略?」之前,先用三張圖快速複習 SW 的工作方式,幫你把觀念釐清:
這些是策略的共同基礎,接下來的快取策略,只是「在攔截時怎麼選擇路徑」的不同。
圖示:當瀏覽器發送請求時,Service Worker 一定會攔截。它可以選擇直接放行到 Server,也能攔截後改成回傳快取,甚至完全阻擋請求。
圖示:Service Worker 在請求網路資源時,除了把資料回給瀏覽器,也會把回應複製一份存到快取,下次就能更快命中。
圖示:網路中斷後,請求無法送到伺服器,Service Worker 仍能回應快取中的資源,讓網站在離線狀態下依舊可用。
在 Service Worker 的 fetch
事件裡,我們可以決定「要回快取,還是要呼叫網路」。但不同應用場景需求不同:
這些就是常見的三種快取策略。它們沒有絕對好壞,而是要根據需求選擇。
優先讀取快取,如果快取裡沒有,才去呼叫網路。常用於圖片、字型、版本固定的 JS/CSS 等資源。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request);
})
);
});
優點:速度快,體驗好。
缺點:可能回傳舊資料,除非重新部署 SW。
優先打網路,如果網路失敗(例如離線),才回快取。常用於動態內容: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))
);
});
優點:確保資料最新。
缺點:若網路慢,使用者會等很久;若網路斷,才會退回快取。
先回快取(讓使用者秒開),同時在背景打網路,等新資料回來再更新快取。下次開啟就能看到更新後的結果。
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 設計上標示)。
昨天 Day 24 - 從頭瞭解 Service Worker 已經提過 SW 的「更新陷阱」。這是最常讓人困惑的地方,這裡再用更直白的方式說一次:
我改了
sw.js
,重新整理網頁,但看到的還是舊版本!🤔
原因在於:新版本要等舊頁面都關閉,才能 activate。
這樣的設計是為了保證:同一個頁面全程只被一個 SW 控制,避免中途換控造成:
可以,但有限制:
self.skipWaiting()
:跳過 waiting,直接進入 activate,即使還有舊分頁存在。可能導致不同分頁狀態不一致。clients.claim()
:讓已啟用的新 SW 立即接管 「尚未被任何 SW 控制」 的分頁(例如第一次載入的頁面)。但已被舊 SW 控制的分頁,仍需重新整理才能切換成新 SW。skipWaiting()
與 clients.claim()
,不過要小心可能引發不同分頁狀態不一致的情況。今天我們認識了三種常見的快取策略:
並且補充了 更新陷阱:為什麼新版本 SW 沒有馬上生效,以及該如何處理。
👉 歡迎追蹤這個系列,我會從 Canvas 開始,一步步帶你認識更多 Web API 🎯