Service Worker
16 Jul 2017Service Worker 讓網頁能擁有與 App 一樣的離線和訊息推播功能。關於離線功能,試想,在使用 App 收信、寫信、刪除信件等動作,都需要將結果丟回伺服器儲存,但在某些環境下並無法一直使用網路連線,因此必須使用一種機制,讓我們仍能順暢的使用這些功能,待網路正常連線,再將剛才所執行的一切動作反應回伺服器。關於訊息推播功能,請參考這裡。
Service Worker 的生命週期
Service Worker 的生命週期有以下幾個步驟
圖片來源:Browser push notifications using JavaScript
下文會先說明瀏覽器支援度檢查、Service Worker 的註冊(Register)與下載(Download),然後再接上 Service Worker 的生命週期中的安裝(Install)、啟動(Active)、閒置(Idle)、發生錯誤(Error)、存取(Fetch)、收發訊息(Message)和結束(Terminated)。
瀏覽器支援度檢查
目前支援 Service Worker 基本功能的主流瀏覽器有 Chrome(40+)、Firefox(44+)和 Opera(24+)。若想知道其他功能的支援度,可查看這裡。
if ('serviceWorker' in navigator) {
// 瀏覽器支援 Service Worker,可使用!
}
註冊(Register)與下載(Download)
Service Worker 在註冊完成後,瀏覽器才會開始執行背景安裝。而不管註冊成功或失敗,可使用 Callback 做後續處理。
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/serviceWorker.js') // 註冊 Service Worker
.then(function(reg) {
console.log('Registration succeeded. Scope is ' + reg.scope); // 註冊成功
})
.catch(function(error) {
console.log('Registration failed with ' + error); // 註冊失敗
});
}
備註
- 只有 https 和 localhost 才能使用 Service Worker。
- Scope 為 Service Worker 的運作範圍。若沒有設定 Scope,運作範圍預設為整個 Domain;若有設定 Scope,則為這個資料夾之下。如上例,由於沒有設定 Scope,因此整個 localhost 都可以使用。但若設定
navigator.serviceWorker.register('/serviceWorker.js', { scope: '/test/' })
,則限定只能運作於「localhost/test/」之下。
安裝(Install)
當 Service Worker 註冊成功後,瀏覽器會開始安裝,即觸發 install 事件。install 事件要做的事情是啟動瀏覽器的離線快取能力,說白話就是將cache.addAll()
內設定的檔案儲存起來,讓 App 能離線執行這些功能。event.waitUntil()
確保 Service Worker 在安裝完畢後才去快取這些檔案。
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll(['/javascripts/service_workers_test/hello.js']);
}),
);
});
啟動(Active)
安裝成功後即啟動。注意,當有多個 Service Worker 時,新安裝的 Service Worker 並不會馬上啟動,必須關閉舊有的所有 Service Worker 後,新的才能啟動。為了節省 Service Worker 的空間,在這裡也可刪除前一個快取的資料。
this.addEventListener('activate', function(event) {
console.log('activated!');
});
存取(Fetch)
當有任何在 Service Worker 控制範圍底下的 HTTP 請求發出時,都會觸發 fetch 事件,在本例的範圍是http://localhost:3000/
。fetch event 會劫持(hijack)這個 HTTP 請求,讓我們能利用respondWith()
處理後續回應。caches.match(event.request)
檢查發出的 HTTP Request 在快取中是否有相符的項目,若有符合則使用 Service Worker 回應,若無符合則發出真正的 HTTP Request 取得回應,並加入快取以供之後使用。因此,在這裡讓我們能設定客製化的回應。
概念如下圖。
圖片來源:Custom responses to requests
例如,存取 hello.js 這個檔案(舉例來說,直接在網址列打上http://localhost:3000/javascripts/service_workers_test/hello.js
),就會得到「Hello from your friendly neighbourhood service worker!」這個回應。
this.addEventListener('fetch', function(event) {
// 目前存取的資訊
console.log('Handling fetch event for', event.request.url);
// 劫持 HTTP Request
event.respondWith(
// 檢查快取中是否有可用的資源
caches.match(event.request).then(function(response) {
if (response) {
// 使用 Service Worker 回應
return new Response('<p>Hello from your friendly neighbourhood service worker!</p>', {
headers: { 'Content-Type': 'text/html' },
});
} else {
console.log('No response found in cache. About to fetch from network...');
}
// Service Worker 沒有設定相對應的回應,發出 HTTP Request
return fetch(event.request)
.then(function(response) {
console.log('Response from network is:', response);
// 加入快取供之後使用
return caches.open('v1').then(function(cache) {
cache.put(event.request, response.clone());
return response;
});
})
.catch(function(error) {
// 錯誤處理
console.error('Fetching failed:', error);
throw error;
});
}),
);
});
收發訊息(Message)
收發訊息。
this.addEventListener('message', function(e) {
// e.source is a client object
e.source.postMessage('Hello! Your message was: ' + e.data);
});
function sendMessage(message) {
console.log('Send message...');
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
sendMessage('Hello World!');
Demo
加入快取供之後。
開發工具
Chrome Server Worker Internals
chrome://serviceworker-internals/
Chrome DevTools
Chrome 瀏覽器按下 F12 進入開發者模式後,可看到目前 Service Worker 的狀況。
查看各家瀏覽器支援度
the Can I Use Service Workers table
備註
這篇文章還有許多需要補充的地方,會慢慢補起來。