注意在isSecureContext
為false
的環(huán)境下嗜逻,拿不到navigator.serviceWorker
1敷鸦、可以攔截請求玛界,并允許我們自己設(shè)置響應(yīng):
// serviceworker.js
self.onfetch = event => {
console.log('攔截到:', event.request.url);
const myResponseBody = new Blob();
// myResponseBody返回可以是text照皆、json急鳄、二進(jìn)制等谤民,需要同時(shí)設(shè)置響應(yīng)頭中與之相匹配的content-type
const myResponse = new Response(myResponseBody,
{
headers: new Headers({
"content-type": "text/plain; charset=UTF-8",
"my-header": "wxm"
}),
status: 200,
statusText: 'YES!'
})
event.respondWith(myResponse);
};
image.png
respondWith
也可以傳一個(gè)Promise
實(shí)例來延遲返回
event.respondWith(new Promise(resolve => {
setTimeout(() => {
resolve(new Response('ok'))
}, 1000);
}));
1.1 攔截的范圍 scope
navigator.serviceWorker.register('./sw.js', { scope: './' })
不管這個(gè)sw.js是被哪個(gè)window引入進(jìn)來的,被攔截的范圍跟scope有關(guān)(scope需要在sw.js所在的路徑范圍之內(nèi))
當(dāng)html地址和scope匹配疾宏,則會(huì)被攔截张足,被攔截的html下發(fā)送的fetch、XHR坎藐、link为牍、script、img顺饮、都能攔截
1.2 在devTools中查看和關(guān)閉
image.png
1.3 生命周期
-
注冊 → 安裝 → 等待 → 激活
image.png
→ 第一次激活這個(gè)sw時(shí)吵聪,它監(jiān)聽不到本頁面的請求,只有新打開或刷新才可以兼雄。
→ 為了讓它立即接管所有的客戶端吟逝,使用self.clients.claim()
,表示在激活時(shí)立即讓當(dāng)前的sw管之前未被接管的clients
→ activate 事件通常發(fā)生在以下情況:① sw首次安裝后激活 ② sw發(fā)生了更新赦肋,跳過或已完成等待階段
- 更新:
當(dāng)sw.js更新時(shí)块攒,如下圖#170
安裝后等待(這個(gè)過程會(huì)觸發(fā)170的install)
但只有等到#169
不再控制任何client
,則激活170(觸發(fā)170的activate)
而self.skipWaiting()
會(huì)跳過這個(gè)等待階段佃乘,立即激活新的(新sw在激活之前囱井,client仍然受老sw的控制)
image.png
-
點(diǎn)擊stop
image.png
2、利用它可以攔截響應(yīng)這個(gè)特性趣避,我們可以做什么
- 流式下載文件
- 監(jiān)控資源加載錯(cuò)誤(或超時(shí))庞呕,進(jìn)行錯(cuò)誤上報(bào)
- 配合Cache Storage做離線緩存
2.1 用處一:流式下載文件
- 步驟:
- 創(chuàng)建一個(gè)
TransformStream
- 用文件名創(chuàng)建一個(gè)下載鏈接,向
serviceworker
發(fā)送下載鏈接和TransformStream
的readable
程帕,sw
中用map
存下來 - 用
iframe
去加載這個(gè)下載鏈接(會(huì)被sw
的onfetch
攔截住练,sw
拿到url
,從自己的map
中取出readable
流作為響應(yīng)愁拭,此時(shí)彈出文件保存框) - 把
TransformStream
的writable
返回出去 - 外部會(huì)獲取到一個(gè)可寫流讲逛,
fetch url
,并把結(jié)果寫入這個(gè)可寫流(當(dāng)這個(gè)可寫流沒有close時(shí)岭埠,這個(gè)以可讀流為響應(yīng)的請求會(huì)一直處于pending
狀態(tài))
var writableStream = await getWritableStream('wxm.jpg'); // 獲取到一個(gè)可寫流
var writer = writableStream.getWriter();
fetch('/mrp/common/images/big.jpg').then(res => {
const totalSize = parseInt(res.headers.get('content-length'));
console.log('資源大姓祷臁:', totalSize);
let loadedSize = 0;
const reader = res.body.getReader();
reader.read().then(function handleResult(result) {
// console.log({ done: result.done });
if (result.done) {
console.log('下載結(jié)束');
writer.close();
return;
}
loadedSize += result.value.length;
console.log('下載進(jìn)度:', parseInt(loadedSize / totalSize * 100));
// 把fetch的結(jié)果不斷寫入這個(gè)可寫流中
writer.write(result.value).catch(error => {
writer.close();
});
return reader.read().then(handleResult);
});
});
-
要下載的圖片路徑的拼接:
雖然xhr蔚鸥、fetch
等請求都能被service worker
攔截到,但是只有iframe许赃、location止喷、window.open
這種方式發(fā)出的請求,才能彈出文件保存彈窗图焰。而后者只有與scope
相匹配時(shí)才能被攔截到启盛。
所以組裝下載鏈接時(shí),要加scope
前綴技羔。例如:需要下載一個(gè)文件名為wxm.jpg的圖片,scope
為http://localhost:4000/mrp/baseInfoV2/SPU/
卧抗,則拼接后的地址為:http://localhost:4000/mrp/baseInfoV2/SPU/wxm.jpg
function getDownloadUrl(scope, fileName) {
return scope + '/' + fileName
}
- 代碼:
async function getWritableStream(fileName) {
fileName = encodeURIComponent(fileName);
let [sw, scope] = await registerSw(); // 先注冊sw藤滥,無則注冊,有則返回
const ts = new TransformStream();
const downloadUrl = getDownloadUrl(scope, fileName); // 組裝一個(gè)下載鏈接
const readableStream = ts.readable
sw.postMessage({ downloadUrl, fileName, readableStream }, [readableStream]); // 讓sw內(nèi)部先把這個(gè)readableStream存下來
makeIframe(downloadUrl); // 用iframe加載這個(gè)downloadUrl
return ts.writable;
}
function registerSw() {
return navigator.serviceWorker.getRegistration('/mrp/baseInfoV2/SPU').then(swReg => {
// scope必須是在sw.js的路徑以下
return swReg || navigator.serviceWorker.register('/mrp/sw.js', {
scope: '/mrp/baseInfoV2/SPU'
})
}
).then(swReg => {
let scope = swReg.scope;
const swRegTmp = swReg.installing || swReg.waiting
return swReg.active ? [swReg.active, scope] : new Promise(resolve => {
swRegTmp.addEventListener('statechange', fn = () => {
if (swRegTmp.state === 'activated') {
swRegTmp.removeEventListener('statechange', fn)
resolve([swReg.active, scope]);
}
})
})
})
}
// 在sw.js中攔截
self.addEventListener('install', () => {
self.skipWaiting() // 安裝完成后并不進(jìn)入等待階段社裆,而是立即激活新的service worker
})
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim())
// event.waitUntil 表示延緩activate事件的完成拙绊,直到其中的Promise被解決
// `self.clients.claim()` 表示在激活時(shí)立即讓當(dāng)前的Service Worker 接管之前未被接管的clients
// 這樣做可以確保新的Service Worker 立即控制所有客戶端,而不需要等到下一次加載頁面時(shí)才能生效
})
const map = new Map();
self.onmessage = event => {
map.set(event.data.downloadUrl, {
fileName: event.data.fileName,
readableStream: event.data.readableStream
});
}
self.onfetch = event => {
const url = event.request.url
const saved = map.get(url)
if (!saved) return null
map.delete(url)
let { fileName, readableStream } = saved;
const responseHeaders = new Headers({
'Content-Type': 'application/octet-stream; charset=utf-8',
'Content-Disposition': "attachment; filename*=UTF-8''" + fileName,
// To be on the safe side, The link can be opened in a iframe.
// but octet-stream should stop it.
'Content-Security-Policy': "default-src 'none'",
'X-Content-Security-Policy': "default-src 'none'",
'X-WebKit-CSP': "default-src 'none'",
'X-XSS-Protection': '1; mode=block',
'Cross-Origin-Embedder-Policy': 'require-corp'
})
event.respondWith(new Response(readableStream, { headers: responseHeaders }))
};
2.2 用處二:監(jiān)控資源加載錯(cuò)誤(或超時(shí))泳秀,進(jìn)行錯(cuò)誤上報(bào)
const MAX = 1000; // 不能超過1s
const onFetch = event => {
const url = event.request.url
event.ajaxStart = Date.now(); // 記錄請求開始
event.timeoutTimer = setTimeout(() => { // 設(shè)置一個(gè)定時(shí)器來報(bào)告請求超時(shí)
console.log('請求超時(shí)了:', url);
}, MAX);
event.respondWith(fetch(event.request).then(response => {
// 如果沒超時(shí)标沪,就清除定時(shí)器
if (Date.now() - event.ajaxStart <= MAX) {
clearTimeout(event.timeoutTimer);
}
return response;
}));
}
2.3 用處三:window.caches
將一些資源請求的response
存到caches
中,可以作為請求失敗的兜底嗜傅。
從緩存中取出response 并作為響應(yīng) |
cache Storage中沒有緩存金句,從services worker中fetch | 到服務(wù)端的請求 |
---|---|---|
- image.png
|
- image.png
|
服務(wù)端.png
|
caches
里面可以包含多個(gè)Cache
實(shí)例(下圖中名為v1
和v2
),每個(gè)Cache
實(shí)例可以存儲多個(gè)【Request和Response實(shí)例的鍵值對】吕嘀,以對網(wǎng)絡(luò)請求進(jìn)行緩存
image.png