[FE] Service Worker、Web Worker 和 Shared Worker

Todd-avatar-img
發佈於FE
更新 發佈閱讀 21 分鐘
Service Worker、Web Worker 和 Shared Worker

Service Worker

可以當作 Client Side 的攔截器,獲取網站內容的請求判斷(像是 axios 的 interceptor 或是 nginx 的 proxy),依據條件進行動作,可以使用 Cache Storage 和 IndexDB,基於事件驅動,並有自己的生命週期。註冊 Service Worker

'serviceWorker' in navigator &&
window.addEventListener('load', () => {
navigator.serviceWorker.register(new URL('./sw.js', import.meta.url));
});

Life Cycle

  • install 每個 Service Worker 只會執行一次,直到 Service Worker 更新
  • activate Service Worker 準備好控制 Client Site
  • activated完成 activate 後觸發的 event

透過 Life Cycle 進行 precaching 的範例

self.addEventListener('install', (event) => {
const cacheKey = '...';

event.waitUntil(caches.open(cacheKey).then((cache) => {

return cache.addAll([
...
]);
}));
});

在 install 時,先進行 precaching 並透過 event.waitUntil  處理非同步的 function。

self.addEventListener('activate', (event) => {
const cacheAllowList = [...];

event.waitUntil(caches.keys().then((keys) => {
return Promise.all(keys.map((key) => {
if (!cacheAllowList.includes(key)) {
return caches.delete(key);
}
}));
}));
});

在 activate 時刪除(修改) cache。

Update event

Service Worker 會在以下情況觸發 update 事件。

  • 使用者瀏覽的 url 在 Service Worker scope 內
  • 更改 scope 或是 Service Worker 的路徑 (不推薦)
  • 在過去 24H 內有觸發過 sync  或是 push  事件
  • 手動觸發
    • navigator.serviceWorker.ready.then((registration) => {
      registration.update();
      });

觸發更新後,Service Worker 將會下載新版本的 Service Worker 但並不會立刻激活,直到沒有打開的頁面是由舊的 Service Worker 控制時才會被激活。

Cache

Service Worker 可以搭配 JS 的 Cache Interface 建立 Cache,與 HTTP 的 Cache Header 並不相同,JS 的 Cache Interface 在較高的層級,完全獨立於 HTTP Cache。例如建立圖片的 Cache

self.addEventListener("fetch", (event) => {
if (event.request.destination === "image") {
event.responseWith(
cache.open(cacheName).then((cache) => {
return cache.match(event.request).then((cacheResponse) => {
if (cacheResponse) {
return cacheResponse;
}
return fetch(event.request.url).then((fetchResponse) => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
});
})
);
}
});


Service Worker 搭配 Cache Interface 可以實現不同的 Cache Strategies。


Cache Only

raw-image

所有的 Resource 只透過 Cache 獲得,一開始進行資源的 Precaching ,直到 Service Worker 更新後才會進行更新。

// asset request url
const precachedAssets = [
'/pic1.jpg',
'/pic2.jpg',
'/pic3.jpg'
];

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

self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
const isPrecachedRequest = precachedAssets.includes(url.pathname);

if (isPrecachedRequest) {
event.responseWith(cache.open(cacheName).then(cache => {
return cache.match(event.request.url);
}));
} else {
// go to the network
return;
}
});

Network Only

raw-image

與 Cache Only 完全相反,沒有 Cache 只向 Network 獲取 Resource。

Cache First, falling back to network

raw-image

如果 Cache 沒有我們要的資源則從 Network 獲取,有的話優先從 Cache 獲得。

self.addEventListener("fetch", (event) => {
event.reponseWith(
caches.open(cacheName).then((cacheResponse) => {
if (cacheResponse) {
return cacheResponse;
}
return fetch(event.request).then((fetchResponse) => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
})
);
});

適合用於 CSS, JavaScript, Image, Font 會經過 hash 的靜態資源。

Network First, falling back to cache

raw-image

優先向 Network 獲取資源,並將獲得的 Response 儲存至 Cache,如果是在 Offline 的情況下,則透過 Cache 獲得資料。

self.addEventListener("fetch", (event) => {
event.responseWith(
caches.open(cacheName).then((cache) => {
return fetch(event.request.url)
.then((fetchResponse) => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
})
.cache(() => {
return cache.match(event.request.url);
});
})
);
});

適合希望獲取最新的資料,但若在 Offline 的時候可以獲取最新的 Cache,例如 HTML 或是 API。

State-while-revalidate

優先考量獲取資源的速度,並在後台進行更新。

self.addEventListener("fetch", (event) => {
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;
});
})
);
});

適合用於需要保持資源狀態在最新,但並不是最重要的資料。

其他的用法

Web Push API

由 Client Side 向第三方 Server(Mozila, Google, Apple 等) 進行 Web Push 的註冊,Client Side 會帶著 VAPID Public Key 傳送給 Server,之後將獲取到的訂閱訊息傳送給 Bacdend。

async () => {
const notificationPermission = await Notification.requestPermission();
if (notificationPermission === "granted") {
const registration = await navigator.serviceWorker.ready;
const notificationSub = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:
"...", // VAPID public key
});
await fetch("BE Server", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(notificationSub),
});
}
})();

Backend 透過獲取到的訂閱資訊和 VAPID Private Key 發送 Request。

const vapidKeys = {
privateKey: "VAPID Private Key",
publicKey: "VAPID Public Key",
};
webPush.setVapidDetails(
"mailto:test@localhost.com",
vapidKeys.publicKey,
vapidKeys.privateKey
);
webPush.sendNotification(
req.body,
JSON.stringify({ title: "...", data: "..." })
);


第三方的 Web Push Server 會發送 Notification 給 Service Worker,由 Service Worker 觸發顯示 Notification。

self.addEventListener("push", (event) => {
const data = event.data.json();
const showNotification = async () => {
await self.registration.showNotification(data.title, { body: data.data });
console.log("showNotification");
};
event.waitUntil(showNotification());
});


Background Sync

當使用者網路不穩或是在 Offline 的環境時,將資料進行緩存,等待到有網路的時候在發送請求。

<form id="form">
<input name="name" /> <input name="message" />{" "}
<button type="submit">Send</button>
</form>;

透過 Cache Interface 進行儲存,並註冊 sync  事件。

document.getElementById("form").addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = { name: formData.get("name"), message: formData.get("message") };
const formCache = await caches.open("form");
await formCache.put("/data", new Response(JSON.stringify(data)));
if ("SyncManager" in window) {
const registration = await navigator.serviceWorker.ready;
registration.sync.register("form-sync");
}
});

在 Service Worker 中進行監聽並發送 Request。

self.addEventListener("sync", (event) => {
switch (event.tag) {
case "form-sync": {
async function submitFormData() {
const formCache = await caches.open("form");
const formDataRes = await formCache.match("/data");
const body = await formDataRes.json();
const resp = await fetch("http://localhost:3000/form-sync", {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" },
});
const data = await resp.json();
console.log(data);
}
event.waitUntil(submitFormData());
break;
}
}
});

Period Background Sync

Interval 的 Background Sync,需要權限(User 有安裝 PWA)才可以進行操作,並依據 Site Engagement Score 來決定 sync 的頻率。

Web Worker

從瀏覽器建立多個 thread 處理任務,主要用於處理可能會使用到大量運算且並不希望影響到使用者的瀏覽體驗。建立 Web Worker。

const worker = new Worker(new URL('./workerPath.js', import.meta.url));

透過 postMessage  進行溝通。

// browser

worker.postMessage({ data: "from app.js" });
worker.addEventListener("message", (event) => {
console.log(event.data);
});

// worker

self.postMessage({ data: "hello from worker.js" });
self.addEventListener("message", (event) => {
console.log(event.data);
});

關閉 Worker。

// browser

worker.terminate();

//worker

self.close();

在 Worker 中關閉的話, Worker 會執行完 Event Loop 後才會關閉。瀏覽器中則會直接中斷關閉。

Shared Worker

Web Worker 的一種,可以在不同的頁面相同 Domain 分享訊息(某一頁面登入,其餘頁面皆改變狀態),或是節省資源(一個 Domain 只建立一個 Websocket 連線)。


留言
avatar-img
Hello Todd
2會員
5內容數
FE Developer
Hello Todd的其他內容
2024/12/23
本篇文章介紹了網頁元素的 Box 佈局,細述 Content Box、Padding Box、Border Box 和 Margin Box 的結構,並探討了 Intrinsic 與 Extrinsic Size 的特性。
Thumbnail
2024/12/23
本篇文章介紹了網頁元素的 Box 佈局,細述 Content Box、Padding Box、Border Box 和 Margin Box 的結構,並探討了 Intrinsic 與 Extrinsic Size 的特性。
Thumbnail
2024/06/09
透過GraphQL提供的分頁方式,優化後端讀取資料的效能,避免過度讀取舊資料及準確指定特定項目。同時,利用Local-only field達成資料的整理或再次經過計算,提升管理和重複使用的效能。
Thumbnail
2024/06/09
透過GraphQL提供的分頁方式,優化後端讀取資料的效能,避免過度讀取舊資料及準確指定特定項目。同時,利用Local-only field達成資料的整理或再次經過計算,提升管理和重複使用的效能。
Thumbnail
2024/01/28
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Thumbnail
2024/01/28
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Thumbnail
看更多
你可能也想看
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
Thumbnail
前面已經安裝好IIS後,並且也新建站台了,那麼接下來這篇就會分享如何使用它
Thumbnail
  在開始操作Web Service之前,要先做一些前置作業IIS的安裝,再開始建置Web Service相關內容,最後就是連線測試。   IIS(Internet Information Services)是網際網路資訊服務,可以讓網站使用HTTP/HTTPS、FTP/FTPS、SMTP 等等的
Thumbnail
  在開始操作Web Service之前,要先做一些前置作業IIS的安裝,再開始建置Web Service相關內容,最後就是連線測試。   IIS(Internet Information Services)是網際網路資訊服務,可以讓網站使用HTTP/HTTPS、FTP/FTPS、SMTP 等等的
Thumbnail
一、什麼是Web Service?   簡單說就是「服務」的概念,人與人間、電腦與電腦間都是一樣的,一個是人與人的一來一回交流,媒介是語言中文,另一個則是個人電腦與伺服器的交流,媒介是HTTP/Internet,那麼有了媒介,就會有共同格式才能做
Thumbnail
一、什麼是Web Service?   簡單說就是「服務」的概念,人與人間、電腦與電腦間都是一樣的,一個是人與人的一來一回交流,媒介是語言中文,另一個則是個人電腦與伺服器的交流,媒介是HTTP/Internet,那麼有了媒介,就會有共同格式才能做
Thumbnail
Service Worker 是用於客戶端的攔截器,可以使用 Cache Storage 和 IndexDB,並有自己的生命週期。Web Worker 用於處理可能會使用到大量運算且不希望影響到使用者體驗的任務。Shared Worker 可以在相同 Domain 的不同頁面上共享訊息。
Thumbnail
Service Worker 是用於客戶端的攔截器,可以使用 Cache Storage 和 IndexDB,並有自己的生命週期。Web Worker 用於處理可能會使用到大量運算且不希望影響到使用者體驗的任務。Shared Worker 可以在相同 Domain 的不同頁面上共享訊息。
Thumbnail
提到後端工程師,似乎就只是開發 API,但一個複雜的系統其實不太可能只透過 API 就能完成,例如一個簡單的功能,註冊會員,其實是由好幾個不同類型的工作互相配合,您才能收到開通信,才確保資料庫不會有一堆未開通帳號等。所以今天就來聊聊一個系統有幾種不同執行方式的工作。
Thumbnail
提到後端工程師,似乎就只是開發 API,但一個複雜的系統其實不太可能只透過 API 就能完成,例如一個簡單的功能,註冊會員,其實是由好幾個不同類型的工作互相配合,您才能收到開通信,才確保資料庫不會有一堆未開通帳號等。所以今天就來聊聊一個系統有幾種不同執行方式的工作。
Thumbnail
Service worker與Web workers相同,也都是一段運行在瀏覽器後台的腳本,提供一些不需要與頁面直接交互的功能(操作dom),主要處理網路相關的問題,可以攔截網路請求進行相對應的優化動作,我們把它想像成與伺服器之間的代理服務器可能會比較容易理解,當網路環境不佳時便回應快取資源,待網路
Thumbnail
Service worker與Web workers相同,也都是一段運行在瀏覽器後台的腳本,提供一些不需要與頁面直接交互的功能(操作dom),主要處理網路相關的問題,可以攔截網路請求進行相對應的優化動作,我們把它想像成與伺服器之間的代理服務器可能會比較容易理解,當網路環境不佳時便回應快取資源,待網路
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News