/** sw created by xiaogang on 2018/1/12 功能描述:簡單的實現緩存 功能! */ /** * sw更新 方案彙總 * * 一、更新 cacheName 等待瀏覽器重啓以後 新的sw 文件接管 (須要配合 activate 事件) * 二、install event 中 執行 self.skipWaiting(); 只須要sw文件有任何的更新 既可實現更新 (其實觸發了install事件,從新拉取文件實現更新緩存!cache.addAll(cacheFiles) 不會被fetch攔截代理) * * cacheFiles 更新方案彙總 * 一、永不更新 :除非sw 觸發更新了 * 二、實時更新 :增長動態時間戳 永不命中 * 三、定時更新 :timeout 參數控制 (兩種方案實現,業務層 或者 sw層.具體看ajax.js) * 四、下次更新 :優先取緩存,同時異步取更新緩存 * */ const dataUrl = new RegExp('\.s?json'); //異步實時更新的數據 規則 const cacheName = 'sw_cache_update'; /** * 須要緩存的 文件列表 * @type {string[]} */ const cacheFiles = [ './script/ajax.js', './script/page.js', './style/page.css' ]; const cacheWhitelist = ['index.html']; /** * sw install 事件(生命週期) * 推薦作緩存 工做!(也能夠什麼都不作) * * caches.open(cacheName).then() * 緩存相關操做 徹底能夠在頁面加載等任什麼時候候處理。(具體參考 官方文檔 或者 cache 目錄demos) * 只是單純的緩存起來,不配合sw.fetch 無法 作攔截代理。因此推薦在install成功以後 再緩存 * */ self.addEventListener('install', InstallEvent => { //sw 更新以後當即 接管替換舊的 sw (無需 activate 事件) self.skipWaiting(); // console.log(InstallEvent); //waitUntil : InstallEvent.waitUntil( //連接 對應的cache ,並進行下載 & 緩存 數據! caches.open(cacheName).then(cache => { cacheKey(cache, 'cacheFiles added before'); return cache.addAll(cacheFiles).then(data => { console.log(data);//undefined cacheKey(cache, 'cacheFiles added after'); }); }) ); }); /** * fetch 事件(生命週期) * 實現 緩存的關鍵。能夠攔截和代理 頁面的請求! * * 問題: * 怎麼更新!!!! * * */ self.addEventListener('fetch', FetchEvent => { // console.log(FetchEvent); if (dataUrl.test(FetchEvent.request.url)) { //先取緩存 後當即更新緩存 FetchEvent.respondWith( caches.match(FetchEvent.request).then(response => { if (response) { fetchRequest(FetchEvent.request).then(data => { console.log(data); }); return responseTimeout(response, FetchEvent.request) } else { return fetchRequest(FetchEvent.request); } }) ); } else { //優先緩存 FetchEvent.respondWith( caches.match(FetchEvent.request).then(response => { //已經緩存 直接返回 。沒有則從新fetch 同時更新到緩存中! return responseTimeout(response, FetchEvent.request) || fetchRequest(FetchEvent.request) }) ); } }); /** * activate 事件(生命週期) * 緩存更新的關鍵! (緩存管理控制!) * * 一、更新您的服務工做線程 JavaScript 文件。用戶導航至您的站點時,瀏覽器會嘗試在後臺從新下載定義服務工做線程的腳本文件。若是服務工做線程文件與其當前所用文件存在字節差別,則將其視爲「新服務工做線程」。 * 二、新服務工做線程將會啓動,且將會觸發 install 事件。 * 三、此時,舊服務工做線程仍控制着當前頁面,所以新服務工做線程將進入 waiting 狀態。 * 四、當網站上當前打開的頁面關閉時,舊服務工做線程將會被終止,新服務工做線程將會取得控制權。 * 五、新服務工做線程取得控制權後,將會觸發其 activate 事件。 * * //install event中 使用self.skipWaiting(); 可使 sw 更新以後當即 接管替換舊的 sw (無需 activate 事件) */ self.addEventListener('activate', ActivateEvent => { console.log(ActivateEvent); // ActivateEvent.waitUntil( updateCacheName() ); /* * Fixes a corner case in which the app wasn't returning the latest data. * You can reproduce the corner case by commenting out the line below and * then doing the following steps: 1) load app for first time so that the * initial New York City data is shown 2) press the refresh button on the * app 3) go offline 4) reload the app. You expect to see the newer NYC * data, but you actually see the initial data. This happens because the * service worker is not yet activated. The code below essentially lets * you activate the service worker faster. */ return self.clients.claim(); }); /** * 經過 更新cacheName 來 實現更新(即 :刪除舊的 caches[cacheName]) * @returns {Promise<any[]>} */ function updateCacheName() { //caches.keys(): 返回 一個 promise return caches.keys().then(cacheNameList => { console.log(cacheNameList); return Promise.all( cacheNameList.map(_cacheName => { console.log(_cacheName); if (_cacheName !== cacheName) { return caches.delete(_cacheName); } }) ); }) } /** * * @param request * @param response */ function updateCache(request, response) { caches.open(cacheName) .then(function (cache) { cache.put(request, response); }); } /** * * @param request * @returns {Promise<Response>} */ function fetchRequest(request) { /** * 在 fetch 請求中添加對 .then() 的回調。 * 得到響應後,執行如下檢查: * 確保響應有效。 * 檢查並確保響應的狀態爲 200。 * 確保響應類型爲 basic,亦即由自身發起的請求。 這意味着,對第三方資產的請求不會添加到緩存。 * 若是經過檢查,則克隆響應。這樣作的緣由在於,該響應是 Stream,所以主體只能使用一次。因爲咱們想要返回能被瀏覽器使用的響應,並將其傳遞到緩存以供使用,所以須要克隆一份副本。咱們將一份發送給瀏覽器,另外一份則保留在緩存。 * */ return fetch(request).then(response => { // Check if we received a valid response if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // if (cacheWhitelist.indexOf('') >= 0) { } // 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. updateCache(request, response.clone()); //todo updateCache() 也能夠放到 調用以後 自行處理! // fetchRequest(FetchEvent.request).then(data => { // console.log(data); // updateCache(); // }); return responseTimeout(response, request);//response }); } //response 只讀 (無法把 timeout 參數寫入對應的response中去,只能定義一個全局的變量:有點坑!) let _requestTimeout = {}; /** * 定時 timeout 內不重複調用! * sw 層統一封裝 * @param response * @param request * @returns {*} */ function responseTimeout(response, request) { if (!response) { //response 不存在直接 返回獲取 服務端最新的 return response } let _url2Obj = url2Obj(request.url); let _timeout = _url2Obj.timeout || 0; let _resTimeout = _requestTimeout[response.url]; let _now = (new Date()).getTime() / 1000; //單位秒 console.log(response.timeout); if (_timeout && _resTimeout) { console.log(request.url); console.log(_now - _resTimeout); // 請求設置 _timeout & response 有更新 timeout (即 不是第一次) if (_now - _resTimeout > _timeout) { // 過時 刪除 緩存 caches.open(cacheName) .then(function (cache) { cache.delete(request).then(data => { if (data) { delete _requestTimeout[response.url]; } }); }); } //刪除以後當即獲取最新的數據 (或者 下次獲取 最新的數據) // return null; } else { console.log(response); // 請求沒有設置 _timeout 或者 response 沒有 timeout (即 第一次請求) // response.timeout = _now; // response.__proto__.timeout = _now; _requestTimeout[response.url] = _now } console.log('------------_requestTimeout--------------'); console.log(_requestTimeout) return response; } /** * 沒啥 實際做用。 * @param cache * @param tips */ function cacheKey(cache, tips) { cache.keys().then(cacheKeyList => { console.log(`-------caches[${cacheName}]------${tips}--------------`); console.log(cacheKeyList);//request 對象 }); } function url2Obj(str) { if (!str) { //單頁面 hash 模式下 search =''; str = location.search || location.hash || location.href; } var query = {}; str.replace(/([^?&=]*)=([^?&=]*)/g, function (m, a, d) { if (typeof query[a] !== 'undefined') { query[a] += ',' + decodeURIComponent(d); } else { query[a] = decodeURIComponent(d); } }); return query; }
實現pwa只需添加 manifest.json 配置文件(記得是域名根目錄下)css