Service Workers是一個為頁面工作的後臺處理器。提供離線web apps是Service Workers目前最讓人感興趣的功能,同時Service Workers能夠管理一個本地的資源快取,當網路連線狀態是正常的時候,這個本地資源快取能夠自動跟伺服器進行同步。這是十分酷的,但我想談一下Service Workers的另一個用途,使用它來管理多個web頁面之間的通訊。
例如,你可能有一個應用開啟在多個瀏覽器頁籤中。Service Workers能夠更新一個頁籤當其他頁簽有一個事件觸發,也可以做到當伺服器發出一個訊息後,所有頁籤的內容將被更新。
一個Service Workers可以控制多個客戶端頁面,例如ServiceWorker會自動的控制它範圍內的所有客戶端頁面,它的範圍是指你站點下的url,通常來講是Service Worker script檔案的路徑。
在這個demo裡,我們將使用三個檔案 client1.html client2.html service-worker.js
首先我們註冊service worker 在 client1.html
<!doctype html>
<html>
<head>
<title>Service Worker - Client 1</title>
</head>
<body>
<script>
if('serviceWorker' in navigator){
// Register service worker
navigator.serviceWorker.register('/service-worker.js').then(function(reg){
console.log("SW registration succeeded. Scope is "+reg.scope);
}).catch(function(err){
console.error("SW registration failed with error "+err);
});
}
</script>
</body>
</html>
複製程式碼
接著我們建立一個基本的 Service worker 在service-worker.js
console.log("SW Startup!");
// Install Service Worker
self.addEventListener('install', function(event){
console.log('installed!');
});
// Service Worker Active
self.addEventListener('activate', function(event){
console.log('activated!');
});
複製程式碼
我不會去解釋他是怎麼工作的,因為在很多地方都有記錄(譯者注:作者的意思是很多地方都有console.log)
我們同時建立了client2.html 我們只會在client1註冊serviceworker,所以不需要有重複的程式碼在這裡。serviceworker執行的時候將會自動的控制它的作用域下的頁面。
<!doctype html>
<html>
<head>
<title>Service Worker - Client 2</title>
</head>
<body>
<script>
</script>
</body>
</html>
複製程式碼
如果你在瀏覽器上訪問client1.html你應該會看到由console.log輸出的註冊資訊。在Chrome(48+)中,你可以在開發工具的“Resouces”選項卡下點選“inspect”,為服務工作者開啟一個檢查器。(譯者注:這個我沒有找到)。當你開啟client2.html在新的瀏覽器頁籤,你可以在開發工具內的“Controlled Clients”找到它
現在我們可以繼續講有趣的東西了。
首先我們讓客戶端發訊息給serviceworker。所有我們需要去加一個訊息的處理在service-worker.js
self.addEventListener('message', function(event){
console.log("SW Received Message: " + event.data);
});
複製程式碼
現在我們增加一個發訊息的函式在兩個客戶端裡
function send_message_to_sw(msg){
navigator.serviceWorker.controller.postMessage("Client 1 says '"+msg+"'");
}
複製程式碼
如果你在客戶端頁面的控制檯內呼叫send_message_to_sw("Hello"),你因該可以在serviceworker的控制檯內看到有訊息顯示
我們可以進一步的讓serviceworker去響應客戶端發來的訊息。實現它我們需要去改良我們的send_message_to_sw函式。我們使用‘Message Channel’,Message Channel能夠提供了一對埠(port)來進行通訊。我們將一個引用連同訊息一起傳送到埠的另一端,所以Service Worker能夠使用它去進行答覆。我們也可以對這些響應訊息做一些處理。為了方便起見,我們還使用Promise來處理等待響應。
譯者注:這裡說的埠(port)用於頁面與serviceworker之間的通訊
function send_message_to_sw(msg){
return new Promise(function(resolve, reject){
// Create a Message Channel
var msg_chan = new MessageChannel();
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = function(event){
if(event.data.error){
reject(event.data.error);
}else{
resolve(event.data);
}
};
// Send message to service worker along with port for reply
navigator.serviceWorker.controller.postMessage("Client 1 says '"+msg+"'", [msg_chan.port2]);
});
}
複製程式碼
在*service-worker.js *我們修改了監聽器,與訊息一起傳送響應在埠
self.addEventListener('message', function(event){
console.log("SW Received Message: " + event.data);
event.ports[0].postMessage("SW Says 'Hello back!'");
});
複製程式碼
現在如果在你的客戶端控制檯執行send_message_to_sw("Hello").then(m => console.log(m))
,你將看到資訊顯示在serviceworker的控制檯裡,在客戶端的控制檯將會有答覆。請注意,我們使用Promise then函式來等待響應和箭頭函式,因為這樣更容易去測定(譯者注:這裡type我翻譯成控制)。
現在我們有了一個讓客戶端發訊息給serviceworker同時serviceworker能夠答覆的機制。您可以使用它讓客戶機檢查長時間執行的流程的狀態,讓serviceworker將訊息轉發給所有客戶端或其他一些很酷的東西。
現在我們允許serviceworker廣播一個事件到所有的客戶端讓所有客戶響應。這與以前使用的機制類似,只是角色顛倒了。
首先我們在客戶端增加一個訊息監聽器,我們增加了測試serviceworker相容性的程式碼,其他的地方几乎相同。
if('serviceWorker' in navigator){
// Handler for messages coming from the service worker
navigator.serviceWorker.addEventListener('message', function(event){
console.log("Client 1 Received Message: " + event.data);
event.ports[0].postMessage("Client 1 Says 'Hello back!'");
});
}
複製程式碼
接著我們給serviceworker增加一個傳送訊息給客戶端的函式。這也跟之前很類似,只是我們需要提供給一個客戶端物件(一個頁面的應用),這個物件能告訴我們要往哪裡發訊息。
function send_message_to_client(client, msg){
return new Promise(function(resolve, reject){
var msg_chan = new MessageChannel();
msg_chan.port1.onmessage = function(event){
if(event.data.error){
reject(event.data.error);
}else{
resolve(event.data);
}
};
client.postMessage("SW Says: '"+msg+"'", [msg_chan.port2]);
});
}
複製程式碼
serviceworker API提供了獲取所有已連線客戶端引用的介面。我們可以將其封裝在一個方便的函式中,以便向所有客戶機廣播訊息(注意,我們再次使用箭頭函式)。
function send_message_to_all_clients(msg){
clients.matchAll().then(clients => {
clients.forEach(client => {
send_message_to_client(client, msg).then(m => console.log("SW Received Message: "+m));
})
})
}
複製程式碼
現在如果我們在serviceworker的控制檯內執行send_message_to_all_clients('Hello')
,您將看到在所有客戶端控制檯中接收到的訊息,以及在serviceworker控制檯中客戶機的響應。