本文涉及幾個知識點:fetch、caches、indexDB 等都不會詳細介紹,僅對於其中某些點帶過javascript
serviceWorker,服務工做線程,顧名思義,只是做爲工做線程存在,不摻和到JS主線程中來,介於 瀏覽器 & 服務器中間層,可攔截指定 client 所發起的全部請求html
目前 PWA(Progress Web App) 的概念很火,大體就是讓 web 也跟 app 同樣,能夠實現添加到桌面、消息推送、離線使用等功能,如 餓了麼 在三月份左右就在H5上整了個 PWA 的頁面。而其中的關鍵點,其實就是離線使用的功能,也就是 sw 在其中的做用。因爲 sw 能夠攔截 client 的請求,也就是可以根據請求,把請求後的 response 用瀏覽器緩存 caches 緩存下來,以實現離線的使用java
說到 sw 的生命週期,就得祭奠出這張圖了
web
步驟分爲如下部分:chrome
- register 這個是由 client 端發起,註冊一個 serviceWorker,這須要一個專門的 sw 處理文件
- install 註冊成功後,此時 sw 中會觸發 install 事件, 需知 sw 中都是事件觸發的方式進行的邏輯調用
- activate 安裝後要等待激活,也就是 activated 事件,只要 register 成功後就會觸發 install ,但不會當即觸發 activated,這個稍後再說
- idle 在 activated 以後就能夠開始對 client 的請求進行攔截處理,sw 發起請求用的是 fetch api
- fetch 激活之後開始對網頁中發起的請求進行攔截處理
- terminate 這一步是瀏覽器自身的判斷處理,當 sw 長時間不用以後,處於閒置狀態,瀏覽器會把該 sw 暫停,直到再次使
- update 瀏覽器會自動檢測 sw 文件的更新,當有更新時會下載並 install,但頁面中仍是老的 sw 在控制,只有當用戶新開窗口後新的 sw 才能激活控制頁面
fetch api
- 發送請求時,默認不會帶上cookie,發送請求時若想帶上cookie,得顯示設定 { credential: 'include' }
- 對於跨域的資源,把模式設置爲跨域 { mode: 'cors' },不然 response 中拿不到對應的數據
caches跨域
- 只能緩存 GET & HEAD 的請求,固然安全起見
- 以上,對於 POST 等類型請求,返回數據能夠保存在 indexDB 中
serviceWorkerpromise
- 註冊的 sw 資源文件,只能監聽該 sw 的路徑 & 以後子路徑的請求,這個怎麼理解呢:也就是若資源是 /app/sw.js ,打印出來 registration.scope === /app/ 則只能監聽 /app/ 下的資源,不能監聽其餘 path,就連 /app 的也不行 !!!這意味着什麼,意味着你在 /app 目錄下注冊的 /app/sw.js,訪問 /app 時不會生效 !
- sw 提供了參數能夠設定 scope 去設定監聽的某一路徑,那麼咱們想讓 /app 生效,得怎麼作呢,其實就是得把 sw.js 放在根目錄 / ,而後設置 { scope: '/app' } 就行了
- 在 sw 中 js 報錯,不會被 client 的監控捕獲到,所以,必需要專門對 sw 的錯誤進行處理
- 基於 a 可知:sw 註冊文件,不能放在 CDN 上,必須在當前意圖監聽的 client 的 domain 下
- Request & Response 中的 body 只能被讀取一次,究其緣由,是其中包含 bodyUsed 屬性,當使用事後,這個屬性值就會變爲 true, 不能再次讀取,解決方法是,把 Request & Response clone 下來: request.clone() || response.clone()
!(function (win) { const sw = win.navigator.serviceWorker const killSW = win.killSW || false if (!sw) { return } if (!!killSW) { sw.getRegistration('/serviceWorker').then(registration => { // 手動註銷 registration.unregister() }) } else { // 表示該 sw 監聽的是根域名下的請求 sw.register('/serviceWorker.js').then(registration => { // 註冊成功後會進入回調 console.log(registration.scope) }).catch(err => { console.error(err) }) } })(window)
第一步:監聽 install 事件,sw 基於事件驅動!瀏覽器
self.addEventListener('install', event => { console.log('installed') ... })
第二步:監聽 activate 事件,sw install 以後不會當即生效,除非新打開頁面,不然當前頁面會一直是舊的 sw 掌控,所以有必要在 activate 後再對當前頁面的緩存等進行必定的處理緩存
// 定義不一樣 path 下的 cahche name const CACHE_NAME = 'TEST1' self.addEventListener('activate', event => { console.log('activated') event.waitUntil( // 刪除舊文件 caches.keys().then(cacheNames => { return Promise.all( cacheNames.map((cacheName) => { return caches.delete(cacheName); }) ); }) ); })
瀏覽器緩存 caches 會一直保存存存存到存不動了,再去刪除某些資源,這個是瀏覽器的行爲,所以仍是建議在每次更改後去刪除一些舊的瀏覽器資源,能夠本身設定
第三步:開始監聽頁面發起的請求
sw 中用的是 fetch api 去請求相應的資源,但不表明 client 中得用 fetch ,全部頁面的請求都會轉變爲 fetch 事件被 sw 捕獲
event.respondWith 接收的是一個 promise 參數,把其結果返回到 client 中
fetch 分爲三大模塊 Header、Request、Response ,這裏並不打算詳說,能夠自行去了解
self.addEventListener('fetch', event => { let { request } = event event.respondWith( // 先從 caches 中尋找是否有匹配 caches.match(request).then(res => { if (res) { return res } // 對於 CDN 資源要更改 request 的 mode if (request.mode !== 'navigate' && request.url.indexOf(request.referrer) === -1) { request = new Request(request, { mode: 'cors' }) } // 對於不在 caches 中的資源進行請求 return fetch(request).then(fetchRes => { // 這裏只緩存成功 && 請求是 GET 方式的結果,對於 POST 等請求,可把 indexDB 給用上 if(!fetchRes || fetchRes.status !== 200 || request.method !== 'GET') { return fetchRes } let resClone = fetchRes.clone() caches.open(CACHE_NAME).then(cache => { cache.put(request, fetchRes) }) return resClone }) }) ) })
調試有幾種方法:
未完待續 ...其實尚未真正把這個用到項目中去,sw 文件的放置路徑就是個大問題,如今全部靜態文件都在 CDN 上,得單獨爲它開個 VIP,能經過 client 的 host 直接訪問到的;另外 餓了麼 以前還很開心的宣佈用上了 PWA ,可是最近不知道爲啥給下線了,懼怕!