如何思考服務工作處理程序
Service Worker 的力量強大,絕對值得學習。這類廣告可讓您為使用者提供更優質的使用體驗。您的網站可以立即載入。離線可以運作。這個軟體可以安裝為專為特定平台設計的應用程式,不僅呈現出精緻的細節,還能隨網路拓展規模,隨心所欲。
但服務工作處理程序和我們網路開發人員習慣的不一樣。不僅容易學習,而且還多了幾隻苦事要小心。
Google Developers 與我最近合作推出 Service Workies 專案,這是讓服務人員瞭解服務工作的免費遊戲。在建構和處理所有服務工作人員的複雜工作時,我遇到了幾難。我才最瞭解到的部分,就是想出幾張描述性的隱喻。我們將在這篇文章中,探討這些心理模型,並分析我們對於癱瘓服務特性的大腦,讓服務人員既是又有挑戰性。
相同,但不同
編寫 Service Worker 的程式碼時,有許多工作感覺會很熟悉。您可以使用您最喜愛的新 JavaScript 語言功能。您可以監聽生命週期事件,就像監聽 UI 事件一樣。您可以按照慣用的承諾管理控制流程。
不過,其他 Service Worker 行為會讓你頭昏腦脹,尤其是當您重新整理頁面,但未套用程式碼變更時,更是如此。
新圖層
一般而言,建立網站時只需要考慮兩個層:用戶端和伺服器。Service Worker 是位於中間的全新層。
服務工作處理程序就像是瀏覽器擴充功能,一種可以在使用者的瀏覽器中安裝。安裝完成後,Service Worker 會使用強大的中層「擴充」網站的瀏覽器。這個 Service Worker 層可以攔截和處理網站發出的所有要求。
Service Worker 層有自己的生命週期,與瀏覽器分頁無關。只重新整理網頁並不足以更新 Service Worker,只是您並不希望重新整理頁面來更新部署在伺服器上的程式碼。每個圖層都有其專屬的更新規則。
在 Service Workies 遊戲中,我們涵蓋了 Service Worker 生命週期的許多詳細資料,我們會針對這個生命週期提供一些實務做法。
功能強大,但功能有限
在您的網站上聘請服務人員會帶來出色的好處,網站可以:
由於服務工作人員可以做到的程度多,他們卻受到設計限制。但無法在和您網站的同步或相同的執行緒中執行任何動作。這表示無法存取以下項目:
- localStorage
- DOM
- 窗戶
好消息是,您的網頁可透過多種方式與服務工作人員溝通,包括直接 postMessage
、一對一訊息管道,以及一對多廣播頻道。
壽命長,但中時短暫
運作中的 Service Worker 會在使用者離開網站或關閉分頁後運作。瀏覽器會保留這個服務工作處理程序,以便使用者下次回訪您的網站時準備就緒。在第一次發出要求之前,服務工作人員有機會攔截要求並掌控網頁。這就是網站可以離線運作的原因,就算使用者沒有連上網際網路,服務工作人員也可以提供網頁本身的快取版本。
在服務工作中,我們會透過 Kolohe (友善的服務工作人員) 攔截和處理要求,以視覺化方式呈現這個概念。
已停止
雖然服務工作處理程序似乎不重要,但卻幾乎隨時都可能停止。瀏覽器不要在目前未執行任何作業的 Service Worker 上浪費資源。停止作業與啟動不同,Service Worker 會保持安裝並啟用。只是想睡覺。下次需要時 (例如處理要求) 時,瀏覽器會將其喚醒。
waitUntil
因為這樣總是有可能進入休眠狀態,因此您的服務工作人員需要能夠讓瀏覽器知道哪些事情需要處理,而且不覺得小睡片刻。此時event.waitUntil()
就能派上用場。這個方法會延長自身使用的生命週期,在準備就緒前,讓物件不會處於停止狀態,也不會進入生命週期的下一階段。這樣我們就有時間設定快取、從網路擷取資源。
以下範例會告知瀏覽器,在建立 assets
快取並填入 sword 圖片之前,系統並未安裝服務工作處理程序:
self.addEventListener("install", event => {
event.waitUntil(
caches.open("assets").then(cache => {
return cache.addAll(["/weapons/sword/blade.png"]);
})
);
});
留意全球狀態
這個開始/停止作業發生時,系統會重設 Service Worker 的全域範圍。因此,請小心不要在 Service Worker 中使用任何全域狀態,否則會在下次重新啟動且狀態與預期不同時感到擔心。
請參考使用全域狀態的範例:
const favoriteNumber = Math.random();
let hasHandledARequest = false;
self.addEventListener("fetch", event => {
console.log(favoriteNumber);
console.log(hasHandledARequest);
hasHandledARequest = true;
});
這個 Service Worker 會在每次要求時記錄一個數字—假設 0.13981866382421893
。hasHandledARequest
變數也會變更為 true
。現在服務工作處理程序暫時處於閒置狀態,因此瀏覽器會將其停止。下次收到要求時,服務工作處理程序必須再次,瀏覽器才會喚醒服務。系統會重新評估指令碼指令碼。現在 hasHandledARequest
已重設為 false
,而 favoriteNumber
是完全不同的:0.5907281835659033
。
您無法依賴 Service Worker 中的儲存狀態。此外,建立訊息管道這類項目可能會造成錯誤:每次服務工作處理程序停止/開始時,您都會獲得全新的執行個體。
在第 3 章中,我們將以視覺化的方式呈現已停止的服務工作人員,因為在等待喚醒期間,服務工作人員會失去所有顏色。
相輔相成,
系統一次只能由一個 Service Worker「控管」您的網頁。但它可以一次「安裝」兩個 Service Worker。變更 Service Worker 程式碼並重新整理頁面後,您實際上並未編輯 Service Worker。Service Worker「無法變更」。您就是要製作全新網站。這個新的 Service Worker (讓我們命名為 SW2) 會安裝,但目前還無法啟用。必須「等待」目前的 Service Worker (SW1) 終止 (使用者離開您的網站時)。
與其他 Service Worker 的快取通訊
安裝時,SW2 可以進行設定,通常是建立及填入快取。但請注意:這個新的 Service Worker 可以存取目前 Service Worker 目前可存取的所有項目。不小心打理時,新的等候服務工作人員也可能致上目前的服務工作人員差不多。以下舉出一些可能造成問題的例子:
- SW2 可能會刪除 SW1 正在使用的快取。
- SW2 可以編輯 SW1 使用的快取內容,導致 SW1 回應該網頁預期之外的資產。
略過等待
服務工作處理程序也可以在安裝完成後,使用具有風險的 skipWaiting()
方法控管網頁。除非您有意嘗試更換故障的 Service Worker,否則這通常不是問題。新的 Service Worker 可能正在使用目前頁面預期外的更新資源,並產生錯誤和錯誤。
開始清理
要避免服務工作處理程序彼此同步,方法就是確保他們使用不同的快取。最簡單的達成方式就是對它們使用的快取名稱建立版本。
const version = 1;
const assetCacheName = `assets-${version}`;
self.addEventListener("install", event => {
caches.open(assetCacheName).then(cache => {
// confidently do stuff with your very own cache
});
});
部署新的 Service Worker 時,您將在卸載 version
後,透過與上一個 Service Worker 分開的快取,讓這個工作站執行需要的工作。
結束清理作業
服務工作處理程序達到 activated
狀態後,就知道工作已接管,而先前的 Service Worker 就會是多餘的。因此,請務必在舊的 Service Worker 之後清理。不僅會尊重使用者快取儲存空間限制,但也有可能能防止意外錯誤。
caches.match()
方法是常用的捷徑,用於從任何相符快取中擷取項目。但會依照快取的建立順序反覆檢查快取。因此,假設您在兩個不同的快取中有兩個版本的指令碼檔案 app.js
:assets-1
和 assets-2
。您的網頁需使用儲存在「assets-2
」中的較新指令碼。不過,如果你沒有刪除舊的快取,caches.match('app.js')
就會從 assets-1
傳回舊快取,這很可能導致你的網站損毀。
前一個 Service Worker 後所需的清除工作,就是刪除新 Service Worker 不需要的所有快取:
const version = 2;
const assetCacheName = `assets-${version}`;
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== assetCacheName){
return caches.delete(cacheName);
}
});
);
});
);
});
要防止服務工作人員彼此發生衝突,必須投入大量心力和專業,但值得一提。
Service Worker 思維
構思適當的服務工作人員時,集思廣益,就能讓你滿懷信心地打造屬於自己的服務。搶先體驗這些功能後,您就能為使用者打造絕佳體驗。
如果希望透過玩遊戲的方式瞭解一切,這正好手氣!前往 Service Workies,您將在當中瞭解 Service Worker 的方法,以便戰勝離線的獸。