——evilrescuer翻譯自Matt Gaunt
寫的[Service Workers: an Introduction](收錄于 谷歌開發者)(https://developers.google.com/web/fundamentals/primers/service-workers/)
一、緩存并返回請求
現在你已經安裝了一個service worker,你可能想要返回一個自己緩存的響應,對吧?
在你安裝完service worker,用戶導航到另一個頁面或者刷新頁面,service worker 就開始接收fetch事件,例子如下:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
這里,我們定義了我們的fetch事件,并在event.respondWith()里,我們從caches.match傳入了一個promise。這個方法從任何你的service worker緩存的文件中查找結果。
如果我們有一個匹配上的response,就返回緩存的值,否則就返回調用fetch的結果,也就是發起網絡請求,并返回收到的數據。
如果我們想累積緩存一個新的請求,可以處理fetch請求的response并把這個response加入緩存,就像下面這樣:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
我們做了這些:
- 在
fetch
請求上,給.then()
增加一個 callback - 一旦我們收到一個響應, 我們做這些檢查:
- 確認響應是合法的.
- 檢查狀態是
200
. - 確認響應類型是 basic(說明是從我們的源發起的請求),也就是不緩存第三方文件發起的請求的響應.
- 如果通過測試,我們 克隆 一個響應。因為響應是一個 流,響應體只會被消耗一次。因為我們需要給瀏覽器返回響應,并且緩存起來以便將來使用, 所以我們需要克隆一下,一個用來發給瀏覽器,一個用來緩存起來。
二、更新一個service worker
有時你的service worker需要更新,此時你需要:
- 更新你的 service worker JavaScript 文件. 當用戶來到你的網頁,瀏覽器嘗試重新下載這個JavaScript 文件。這個文件有任何改變,哪怕是一個字節,都會被認為是新的.
- 你的新 service worker 將會啟動,install的事件會被觸發.
- 舊的service worker還在控制當前頁面,所以新的service worker會進入一個等待的狀態。
- 當前打開的頁面如果被關閉,舊的 service worker會被銷毀,新的開始控制。
- 一旦新的service worker得到控制權,它的activate事件會被觸發.
我們常常在activate的callback中處理緩存管理。注意:如果你在安裝步驟清掉舊緩存,那些仍然有控制權的舊service worker,會立即停止那個緩存文件的工作,所以在這里做比較好。
比如說,我們有一個緩存叫 'my-site-cache-v1',我們希望把這個緩存分割成兩個,一個給頁面用,一個給博客文章用。那在install(安裝)步驟,我們創建兩個緩存:'pages-cache-v1' 和 'blog-posts-cache-v1' ,然后在activate(激活)步驟,我們刪掉我們的舊緩存'my-site-cache-v1'。
下面的代碼會循環查看 service worker 的緩存,刪掉那些不在白名單上的緩存。
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
三、粗糙的邊緣和陷阱
server worker是個新玩意兒。這里提一些問題,希望可以盡早被解決,但是現在還是需要注意一下。
1.安裝失敗的信息無法告知
如果你注冊了一個service worker,但是沒在chrome://inspect/#service-workers or chrome://serviceworker-internals中出現,那可能是因為拋出錯誤,或者一個可被傳輸到event.waitUntil()的rejected promise錯誤
為了解決這個問題,前往 chrome://serviceworker-internals
并打開谷歌開發工具,對安裝事件進行調試。 以及配合這篇文章提到的: 《調試未捕獲的異常》。
2.fetch的默認情況
2.1 沒有credentials憑據
當你使用fetch時,默認情況下,請求是不帶credentials憑據的(比如cookies),如果你需要憑據,可以這樣使用:
fetch(url, {
credentials: 'include'
})
對于同源的URL來說,我們特意這么設置后, 會比 XHR復雜一些。fetch行為和其他的CORS 請求一樣, 比如 <img crossorigin>, 如果你不設置<img crossorigin="use-credentials">,它是不會發cookies的。
2.2 非CORS默認安裝失敗
By default, fetching a resource from a third party URL will fail if it doesn't support CORS. You can add a no-CORS option to the Request to overcome this, although this will cause an 'opaque' response, which means you won't be able to tell if the response was successful or not.
默認情況下,由于沒有CORS,從第三方URL獲取一個資源會失敗。你可以加上一個no-CORS,盡管這會導致一些不透明的響應,因為你不知道響應是否成功。做法如下:
cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {
return new Request(urlToPrefetch, { mode: 'no-cors' });
})).then(function() {
console.log('All resources have been fetched and cached.');
});
譯者注:如果你不了解no-CORS,請查看 request模式-MDN
2.3 處理響應式圖像
對于service worker來說,如果你想在安裝步驟緩存一張圖片,你有幾種選擇:
- 安裝所有可能用到的圖片
- 緩存低分辨率版本圖片
- 緩存高分辨率版本圖片
實際上你應該選擇選項2或3,因為下載所有圖像會浪費存儲空間。
假設您在安裝時選擇低分辨率版本,并且您希望在加載頁面時嘗試從網絡中檢索更高分辨率的圖像,但如果高分辨率圖像失敗,則回退到低分辨率版本。這看起來很好,但有一個問題。
比如我們有下面兩張圖片:
Screen Density Width Height
1x 400 400
2x 800 800
我們可能這么使用:
<img src="image-src.png" srcset="image-src.png 1x, image-2x.png 2x" />
如果我們在2x的顯示器上,瀏覽器會去下載 image-2x.png,如果離線了,你可以用.catch() 緩存,并返回 image-src.png(如果已經緩存上的話)。
但是瀏覽器會期望圖像考慮到2x屏幕上的額外像素,因此圖像將顯示為200x200 CSS像素而不是400x400 CSS像素。唯一的方法是在圖像上設置固定的高度和寬度。
四、學習更多
這里有一系列關于 service worker 的文檔: https://jakearchibald.github.io/isserviceworkerready/resources ,可能對你有用。
(完)