Service Worker 全面進階

JosMcGovern 7年前發布 | 12K 次閱讀 CSS 前端技術

Service Worder 是用來代替 manifest,用來生成緩存的效果的。以前吭哧吭哧的學 manifest 的時候,就發現 MD 好難用。而且 MDN 特意告訴你, manifest 有毒 ,請不要亂用,保不定后面不支持。今兒,我看了下兼容性,呵呵~

人生苦短,及時享樂,前端真坑,不敢亂學。

前方高能,如果覺得生活沒有趣味可以繼續看下去,會讓你的人生更沒有趣味。如果覺得湊合能過,請 ctrl/command + w 。

繼續~

Service Worker 講道理是由兩部分構成,一部分是 cache,還有一部分則是 Worker 。所以,SW(Service Worker) 本身的執行,就完全不會阻礙當前 js 進程的執行,確保性能第一。那 SW 到底是怎么工作的呢?

  • 后臺進程: SW 就是一個 worker 獨立于當前網頁進程。
  • 網絡代理: SW 可以用來代理請求,緩存文件
  • 靈活觸發: 需要的時候吊起,不需要的時候睡眠(這個是個坑)
  • 異步控制: SW 內部使用 promise 來進行控制。

我們先來看看 SW 比較坑的地方,它的 lifecycle

SW 的生命周期

首先,SW 并不是你網頁加載就與生俱來的。如果,你需要使用 SW,你首先需要注冊一個 SW,讓瀏覽器為你的網頁分配一塊內存空間來。并且,你能否注冊成功,還需要看你緩存的資源量決定(有可能失敗,真的有可能)。如果,你需要緩存的靜態資源全部保存成功,那么恭喜您,SW 安裝成功。如果,其中有一個資源下載失敗并且無法緩存,那么這次吊起就是失敗的。不過,SW 是由重試機制的,這點也不算特別坑。

當安裝成功之后,此時 SW 就進入了激活階段(activation)。然后,你可以選擇性的檢查以前的文件是否過期等。

檢查完之后,SW 就進入待機狀態。此時,SW 有兩種狀態,一種是 active,一種是 terminated。就是激活/睡眠。激活是為了工作,睡眠則為了節省內存。這是一開始設計的初衷。如果,SW 已經 OK,那么,你網頁的資源都會被 SW 控制,當然,SW 第一次加載除外。 簡單的流程圖,可以參考一下 google的:

從入門到放棄

上面簡單介紹了 SW 的基本生命周期(實際上,都是廢話),講點實在的,它的兼容性咋樣?

基本上手機端是能用的。

基于 HTTPS

現在,開發一個網站沒用 HTTPS,估計都沒好意思放出自己的域名(太 low)。HTTPS 不僅僅可以保證你網頁的安全性,還可以讓一些比較敏感的 API 完美的使用。值得一提的是,SW 是基于 HTTPS 的,所以,如果你的網站不是 HTTPS,那么基本上你也別想了 SW。這估計造成了一個困難,即,我調試 SW 的時候咋辦? 解決辦法也是有的,使用 charles 或者 fildder 完成域名映射即可。

下面,我們仔細介紹下,SW 的基本使用。

Register

SW 實際上是掛載到 navigator 下的對象。在使用之前,我們需要先檢查一下是否可用:

if ('serviceWorker' in navigator) {
  // ....
}

如果可用,我們就要使用 SW 進行路由的注冊緩存文件了。不過,這里有點爭議。啥時候開始執行 SW 的注冊呢?上面說過,SW 就是一個網絡代理,用來捕獲你網頁的所有 fetch 請求。那么,是不是可以這么寫?

window.addEventListener('DOMContentLoaded', function() {
    // 執行注冊
    navigator.serviceWorker.register('/sw.js').then(function(registration) {

    }).catch(function(err) {

    }); 
  });

這樣理解邏輯上是沒有任何問題的,關鍵在于,雖然 SW 是 worker ,但瀏覽器的資源也是有限的,瀏覽器分配給你網頁的內存就這么多,你再開個 SW(這個很大的。。。),沒有 jank 才怪嘞,而且如果你網頁在一開始加載的時候有動畫展示的話,那么這種方式基本上就 GG 了。 另外,如果算上用戶第一次加載,那么這個卡頓或者延時就很大了。 當然,W3C 在制定相關規范時,肯定考慮到這點, 實際上 SW 在你網頁加載完成同樣也能捕獲已經發出的請求 。所以,為了減少性能損耗,我們一般直接在 onload 事件里面注冊 SW 即可。GOOGLE Jeff Posnick 針對這個加載,專門討論了一下, 有興趣的可以參考一下 。(特別提醒,如果想要測試注冊 SW 可以使用隱身模式調試!!!) 那當我注冊成功時,怎樣查看我注冊的 SW 呢? 這很簡單,直接打開 chrome://inspect/#service-workers 就可以查看,在當前瀏覽器中,正在注冊的 SW。另外,還有一個 chrome://serviceworker-internals ,用來查看當前瀏覽器中,所有注冊好的 SW。 使用 SW 進行注冊時,還有一個很重要的特性,即,SW 的作用域不同,監聽的 fetch 請求也是不一樣的。 例如,我們將注冊路由換成: /example/sw.js

window.addEventListener('DOMContentLoaded', function() {
    // 執行注冊
    navigator.serviceWorker.register('/example/sw.js').then(function(registration) {

    }).catch(function(err) {

    });
  });

那么,SW 后面只會監聽 /example 路由下的所有 fetch 請求,而不會去監聽其他,比如 /jimmy , /sam 等路徑下的。

Install

從這里開始,我們就正式進入 SW 編程。記住,下面的部分是在另外一個 js 中的腳本,使用的是worker 的編程方法。如果,有同學還不理解 worker 的話,可以先去學習一下,這樣在后面的學習中才不會踩很深的坑。 監聽安裝 SW 的代碼也很簡單:

self.addEventListener('install', function(event) {
  // Perform install steps
});

當安裝成功后,我們能使用 SW 做什么呢? 那就開始緩存文件了唄。簡單的例子為:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mysite-static-v1').then(function(cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js'
      ]);
    })
  );
});

此時,SW 會檢測你制定文件的緩存問題,如果,已經都緩存了,那么 OK,SW 安裝成功。如果查到文件沒有緩存,則會發送請求去獲取,并且會帶上 cache-bust 的 query string,來表示緩存的版本問題。當然,這只針對于第一次加載的情況。當所有的資源都已經下載成功,那么恭喜你可以進行下一步了。大家可以參考一下 google demo 。 這里,我簡單說一下上面的過程,首先 event.waitUntil 你可以理解為 new Promise,它接受的實際參數只能是一個 promise,因為,caches 和 cache.addAll 返回的都是 Promise,這里就是一個串行的異步加載,當所有加載都成功時,那么 SW 就可以下一步。另外, event.waitUntil 還有另外一個重要好處,它可以用來延長一個事件作用的時間,這里特別針對于我們 SW 來說,比如我們使用 caches.open 是用來打開指定的緩存,但開啟的時候,并不是一下就能調用成功,也有可能有一定延遲,由于系統會隨時睡眠 SW,所以,為了防止執行中斷,就需要使用 event.waitUntil 進行捕獲。另外,event.waitUntil 會監聽所有的異步 promise,如果其中一個 promise 是 reject 狀態,那么該次 event 是失敗的。這就導致,我們的 SW 開啟失敗。

不穩定加載

不過,如果其中一個文件下載失敗的話,那么這次你的 SW 啟動就告吹了,即,如果其中有一個 Promise 是使用 reject 的話,那就代表著–您這次啟動是 GG 的。那,有沒有其他辦法在保證一定穩定性的前提下,去加載比較大的文件呢? 有的,那你別返回 cache.addAll 就ok了。什么個意思呢? 就這樣:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function(cache) {
    // 不穩定文件或大文件加載
      cache.addAll(
        //...
      );
      // 穩定文件或小文件加載
      return cache.addAll(
        // core assets & levels 1-10
      );
    })
  );
});

這樣,第一個 cache.addAll 是不會被捕獲的,當然,由于異步的存在,這毋庸置疑會有一些問題。比如,當大文件還在加載的時候,SW 斷開,那么這次請求就是無效的。不過,你這樣寫本來就算是一個 trick,這種情況在制定方案的時候,肯定也要考慮進去的。整個步驟,我們可以用下圖表示: FROM GOOGLE

緩存捕獲

該階段就是事關整個網頁能否正常打開的一個階段–非常關鍵。在這一階段,我們將學會,如何讓 web 使用緩存,如何做向下兼容。 先看一個簡單的格式:

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);
      }
    )
  );
});

首先看一下,第一個方法– event.respondWith ,用來包含響應主頁面請求的代碼。當接受到 fetch 請求時,會直接返回 event.respondWith Promise 結果。我們在 worker 中,捕獲頁面所有的 fetch 請求。可以看到 event.request ,這個就是 fetch 的 request 流。我們通過 caches.match 捕獲,然后返回 Promise 對象,用來進行響應的處理。大家看這段代碼時,可能會有很多的疑惑,是的,一開始我看的時候也是,因為,根本沒注釋,有些 name 實際上是內核自帶的。上面的就有:

  • caches: 這是用來控制緩存專門分離出來的一個對象。可以參考: caches
  • fetch: 是現代瀏覽器用來代替 XMLHttpRequest 專門開發出的 ajax 請求。可以參考:fetch 通信

簡單來說,caches.match 根據 event.request ,在緩存空間中查找指定路徑的緩存文件,如果匹配到,那么 response 是有內容的。如果沒有的話,則再通過 fetch 進行捕獲。整個流圖如下:

OK,那現在有個問題,如果沒有找到緩存,那么應該怎么做呢?

  • 啥都不做,等下一次 SW 自己根據路由去緩存。
  • 沒找到,我手動 fetch 然后添加進緩存。

那怎么手動添加呢? 很簡單,自己發送 fetch,然后使用 caches 進行緩存即可。不過,這里又涉及到另外一個概念,Request 和 Response 流。這是在fetch 通信方式 很重要的兩個概念。fetch 不僅分裝了 ajax,而且在通信方式上也做了進一步的優化,同 node 一樣,使用流來進行重用。眾所周知,一個流一般只能使用一次,可以理解為喝礦泉水,只能喝一次,不過,如果我知道了該水的配方,那么我就可以量產該水,這就是流的復制。下面代碼也基本使用到這兩個概念,基本代碼為:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        if (response) {
          return response;
        }

        // 因為 event.request 流已經在 caches.match 中使用過一次,
        // 那么該流是不能再次使用的。我們只能得到它的副本,拿去使用。
        var fetchRequest = event.request.clone();

        // fetch 的通過信方式,得到 Request 對象,然后發送請求
        return fetch(fetchRequest).then(
          function(response) {
            // 檢查是否成功
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // 如果成功,該 response 一是要拿給瀏覽器渲染,而是要進行緩存。
            // 不過需要記住,由于 caches.put 使用的是文件的響應流,一旦使用,
            // 那么返回的 response 就無法訪問造成失敗,所以,這里需要復制一份。
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

那么整個流圖變為:

而里面最關鍵的地方就是 stream 這是現在瀏覽器操作數據的一個新的標準。為了避免將數據一次性寫入內存,我們這里引入了 stream,相當于一點一點的吐。這個和 nodeJS 里面的 stream 是一樣的效果。你用上述哪個流圖,這估計得取決于你自己的業務。

Update

在 SW 中的更新涉及到兩塊,一個是基本靜態資源的更新,還有一個是 SW.js 文件的更新。這里,我們先說一下比較坑的 SW.js 的更新。

SW.js 的更新

SW.js 的更新不僅僅只是簡單的更新,為了用戶可靠性體驗,里面還是有很多門道的。

  • 首先更新 SW.js 文件,這是最主要的。只有更新 SW.js 文件之后,之后的流程才能觸發。SW.js 的更新也很簡單,直接改動 SW.js 文件即可。瀏覽器會自動檢查差異性(就算只有 1B 的差異也行),然后進行獲取。
  • 新的 SW.js 文件開始下載,并且 install 事件被觸發
  • 此時,舊的 SW 還在工作,新的 SW 進入 waiting 狀態。注意,此時并不存在替換
  • 接著,當你現在已經打開的頁面關閉時,那么舊的 SW 則會被 kill 掉。新的 SW 就開始接管頁面的緩存資源。
  • 一旦新的 SW 接管,則會觸發 activate 事件。

整個流程圖為:

如果上述步驟成功后,原來的 SW.js 就會被清除。但是,以前版本 SW.js 緩存文件沒有被刪除。針對于這一情況,我們可以在新的 SW.js 里面監聽 activate 事件,進行相關資源的刪除操作。當然,這里主要使用到的 API 和 caches 有很大的關系(因為,現在所有緩存的資源都在 caches 的控制下了)。比如,我以前的 SW 緩存的版本是 v1 ,現在是 v2 。那么我需要將 v1 給刪除掉,則代碼為:

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['v1'];

  event.waitUntil(
  // 遍歷 caches 里所有緩存的 keys 值
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.includes(cacheName)) {
          // 刪除 v1 版本緩存的文件
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

另外,我那么你不經僅可以用來作為版本的更新,還可以作為緩存目錄的替換。比如,我想直接將 site-v1 的緩存文件,替換為 ajax-v1 和 page-v1 。則,我們一是需要先在 install 事件里面將 ajajx-v1 和 page-v1 緩存套件給注冊了,然后,在 activate 里面將 site-v1 緩存給刪除,實際代碼和上面其實是一樣的:

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['site-v1'];

  event.waitUntil(
  // 遍歷 caches 里所有緩存的 keys 值
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.includes(cacheName)) {
          // 刪除 v1 版本緩存的文件
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

OK,SW.js 更新差不多就是這樣一塊內容。

文件更新

對于文件更新來說,整個機制就顯得很簡單了。可以說,你想要一個文件更新,只需要在 SW 的 fetch 階段使用 caches 進行緩存即可。實際操作也很簡單,一開始我們的 install 階段的代碼為:

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mysite-static-v1').then(function(cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js'
      ]);
    })
  );
});

我們只需要在這里簡單的寫下一下 prefetch 代碼即可。

self.addEventListener('install', function(event) {
  var now = Date.now();
  // 事先設置好需要進行更新的文件路徑
  var urlsToPrefetch = [
    'static/pre_fetched.txt',
    'static/pre_fetched.html',
    'https://www.chromium.org/_/rsrc/1302286216006/config/customLogo.gif'
  ];


  event.waitUntil(
    caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
      var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
      // 使用 url 對象進行路由拼接
        var url = new URL(urlToPrefetch, location.href);
        url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;
        // 創建 request 對象進行流量的獲取
        var request = new Request(url, {mode: 'no-cors'});
        // 手動發送請求,用來進行文件的更新
        return fetch(request).then(function(response) {
          if (response.status >= 400) {
            // 解決請求失敗時的情況
            throw new Error('request for ' + urlToPrefetch +
              ' failed with status ' + response.statusText);
          }
          // 將成功后的 response 流,存放在 caches 套件中,完成指定文件的更新。
          return cache.put(urlToPrefetch, response);
        }).catch(function(error) {
          console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
        });
      });

      return Promise.all(cachePromises).then(function() {
        console.log('Pre-fetching complete.');
      });
    }).catch(function(error) {
      console.error('Pre-fetching failed:', error);
    })
  );
});

當成功獲取到緩存之后, SW 并不會直接進行替換,他會等到用戶下一次刷新頁面過后,使用新的緩存文件。

不過,這里請注意,我并沒有說,我們更新緩存只能在 install 里更新,事實上,更新緩存可以在任何地方執行。它主要的目的是用來更新 caches 里面緩存套件。我們提取一下代碼:

// 找到緩存套件并打開
caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
        // 根據事先定義的路由開始發送請求
      var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
        // 執行 fetch
        return fetch(request).then(function(response) {
          // 緩存請求到的資源
          return cache.put(urlToPrefetch, response);
        }).catch(function(error) {
          console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
        });
      });
    // 使用 promise.all 進行全部捕獲
      return Promise.all(cachePromises).then(function() {
        console.log('Pre-fetching complete.');
      });
    }).catch(function(error) {
      console.error('Pre-fetching failed:', error);
    })

現在,我們已經拿到了核心代碼,那有沒有什么簡便的辦法,讓我們少寫一些配置項,直接對每一個文件進行文件更新教研。 有的!!! 還記得上面的 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);
      }
    )
  );
});

實際上,我們可以將上面的核心代碼做一些變化直接用上:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});

這里比較難的地方在于,我們并沒有去捕獲 fetch(fetchRequest)… 相關內容。也就是說,這一塊是完全獨立于我們的主體業務的。他的 fetch 只是用更新文件而已。我們可以使用一個流圖進行表示:

ok,關于文件的緩存我們就介紹到這里。

用戶更新

現在,為了更好的用戶體驗,我們可以做的更尊重用戶一些。可以設置一個 button,告訴用戶是否選擇緩存指定文件。有同學可能會想到使用 postmessage API,來告訴 SW 執行相關的緩存信息。不過事實上,還有更簡單的辦法來完成,即,直接使用 caches 對象。caches 和 web worker 類似。都是直接掛載到 window 對象上的。所以,我們可以直接使用 caches 這個全局變量來進行搜索。那么該環節就不需要直接通過 SW,這個流程圖可以畫為:

代碼可以參考:

document.querySelector('.cache-article').addEventListener('click', function(event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  // 創建 caches 套件
  caches.open('mysite-article-' + id).then(function(cache) {
    fetch('/get-article-urls?id=' + id).then(function(response) {
      // 返回 json 對象
      return response.json();
    }).then(function(data) {
    // 緩存指定路由
      cache.addAll(data);
    });
  });
});

這里我就不贅述了,簡單來說就是更新一下緩存。

Caches 相關

上面大致了解了一下關于 SW 的基本流程,不過說到底,SW 只是一個容器,它的內涵只是一個駐留后臺進程。我們想關心的是,在這進程里面,我們可以做些什么? 最主要的應該有兩個東西,緩存和推送。這里我們主要講解一下緩存。不過在SW 中,我們一般只能緩存 POST 上面在文件更新里面也講了幾個更新的方式。簡單來說:

簡單的情形上面已經說了,我這里專門將一下比較復雜的內容。

網絡緩存同時干

這種情形一般是用來裝逼的,一方面檢查請求,一方面有檢查緩存,然后看兩個誰快,就用誰,我這里直接上代碼吧:

function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // 通過 promise 的 resolve 特性來決定誰快
    promises = promises.map(p => Promise.resolve(p));
    // 這里調用外層的 resolve
    promises.forEach(p => p.then(resolve));
    // 如果其中有一方出現 error,則直接掛掉
    promises.reduce((a, b) => a.catch(() => b))
      .catch(() => reject(Error("All failed")));
  });
};

self.addEventListener('fetch', function(event) {
  event.respondWith(
    promiseAny([
      caches.match(event.request),
      fetch(event.request)
    ])
  );
});

總是更新

這里就和我們在后臺配置的 Last-Modifier || Etag 一樣,詢問更新的文件內容,然后執行更新:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return fetch(event.request).then(function(response) {
        cache.put(event.request, response.clone());
        return response;
      });
    })
  );
});

先返回后更新

這應該是目前為止最佳的體驗,返回的時候不會影響正在發送的請求,而接受到的新的請求后,最新的文件會替換舊的文件。(這個就是前面寫的代碼):

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});

接下來,我們來詳細了解一下關于 Cache Object 相關的內容。加深印象:

Cache Object

Cache 雖然是在 SW 中定義的,但是我們也可以直接在 window 域下面直接使用它。它通過 Request/Response 流(就是 fetch)來進行內容的緩存。每個域名可以有多個 Cache Object,具體我們可以在控制臺中查看:

并且 Cache Object 是懶更新,實際上,就可以把它比喻為一個文件夾。如果你不自己親自更新,系統是不會幫你做任何事情的。對于刪除也是一樣的道理,如果你不顯示刪除,它會一直存在的。不過,瀏覽器對于每個域名的 Cache Object 數量是有限制的,并且,會周期性的刪掉一些緩存信息。最好的辦法,是我們自己管理資源,官方給出的建議是: 使用版本號進行資源管理 。上面我也展示過,刪除特定版本的緩存資源:

self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v2'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});

Cache Object 操作相關方法

這里,我們就可以將 Cache Object 理解為一個持久性數據庫,那么針對于數據庫來說,簡單的操作就是 CRUD。而 Cache Object 也提供了這幾個接口,并且接口結果都是通過 Promise 對象返回的,成功返回對應結果,失敗則返回 undefined:

  • Cache.match(request, options): 成功時,返回對應的響應流–response。當然,查找的時候使用的是正則匹配,表示是否含有某個具體字段。
    • options:
      • ignoreSearch[boolean]:是否忽略 querystring 的查找。即,我們查找的區域不包括 qs。比如: http://foo.com/?value=bar ,我們不會再搜索 ?value=bar 這幾個字符。
      • ignoreMethod[boolean]:當設置為 true 時,會防止 Cache 驗證 http method,默認情況下,只有 GET 和 HEAD 能夠通過。默認值為 false。
      • ignoreVary[boolean]:當設置為 true 時,表示不對 vary 響應頭做驗證。即, Cache 只需要通過 URL 做匹配即可,不需要對響應頭 vary 做驗證。默認值為 false。
      • cacheName[String]: 自己設置的緩存名字。一般用不到,match 會自動忽略。
cache.match(request,{options}).then(function(response) {
  //do something with the response
});
  • Cache.matchAll(request, options): 成功時,返回一個數組,包含所有匹配到的響應流。options 和上面的一樣,這里就不多說了。
cache.matchAll(request,{options}).then(function(response) {
    response.forEach(function(element, index, array) {
      cache.delete(element);
    });
});
  • Cache.add(url): 這實際上就是一個語法糖。fetch + put。即,它會自動的向路由發起請求,然后緩存獲取到的內容。
cache.add(url).then(function() {
  // 請求的資源被成功緩存
});

# 等同于
fetch(url).then(function (response) {
  if (!response.ok) {
    throw new TypeError('bad response status');
  }
  return cache.put(url, response);
})
.then(res=>{
    // 成功緩存
})
  • Cache.addAll(requests):這個就是上面 cache.add 的 Promise.all 實現方式。接受一個 Urls 數組,然后發送請求,緩存上面所有的資源。
this.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/public/',
        '/public/index.html',
        '/public/style.css',
        '/public/app.js'
      ]);
    })
  );
});
  • Cache.put(request, response): 將請求的資源以 req/res 鍵值對的形式進行緩存。如果,之前已經存在對應的 req(即,key 值),那么以前的值將會被新值覆蓋。
cache.put(request, response).then(function() {
  // 成功緩存
});
  • Cache.delete(request, options): 用來刪除指定的 cache。如果你不刪除,該資源會永遠存在(除非電腦自動清理)。
  • Cache.keys(request, options): 返回當前緩存資源的所有 key 值。
cache.keys().then(function(keys) {
    keys.forEach(function(request, index, array) {
      cache.delete(request);
    });
  });

可以查看到上面的參數都共同的用到了 request 這就是 fetch 套件里面的請求流,具體,可以參考一下前面的代碼。上面所有方法都是返回一個 Promise 對象,用來進行異步操作。

上面簡單介紹了一下 Cache Object,但實際上,Cache 的管理方式是兩級管理。即,最外層是 Cache Storage ,下一層是 Cache Object 。

Cache Storage

瀏覽器會給每個域名預留一個 Cache Storage(只有一個)。然后,剩下的緩存資源,全部都存在下面。我們可以理解為,這就是一個頂級緩存目錄管理。而我們獲取 Cache Object 的唯一途徑,就是通過 caches.open() 進行獲取。這里,我們就可以將 open 方法理解為 沒有已經存在的 Cache Object 則新建,否則直接打開 。它的相關操作方法也有很多:

  • CacheStorage.match(request,{options}):在所有的 Cache Object 中進行緩存匹配。返回值為 Promise
caches.match(event.request).then(function(resp) {
  return resp || fetch(event.request).then(function(r) {
    caches.open('v1').then(function(cache) {
      cache.put(event.request, r);
    });
    return r.clone();
  });
});
  • CacheStorage.has(cacheName): 用來檢查是否存在指定的 Cache Object。返回 Boolean 代表是否存在。
caches.has('v1').then(function(hasCache) {
 // 檢測是否存在 Cache Object Name 為 v1 的緩存內容
  if (!hasCache) {
    // 沒存在
  } else {
    //...
  }
}).catch(function() {
  // 處理異常
});
  • CacheStorage.open(cacheName): 打開指定的 Cache Object。并返回 Cache Object。
caches.open('v1').then(function(cache) {
    cache.add('/index.html');
  });
  • CacheStorage.delete(cacheName): 用來刪除指定的 Cache Object,返回值為 Boolean:
caches.delete(cacheName).then(function(isDeleted) {
  // 檢測是否刪除成功
});

# 通過,可以通過 Promise.all 的形式來刪除多個 cache object
Promise.all(keyList.map(function(key) {
        if (cacheList.indexOf(key) === -1) {
          return caches.delete(keyList[i]);
        }
      });
  • CacheStorage.keys(): 以數組的形式,返回當前 Cache Storage 保存的所有 Cache Object Name。
event.waitUntil(
 caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (['v1','v2'].indexOf(key) === -1) {
          return caches.delete(keyList[i]);
        }
      });
    })
    );

上面就是關于 Cache Storage 的所有內容。

這里放一張自己寫的總結圖吧:

 

來自:https://www.villainhr.com/page/2017/01/08/Service Worker 全面進階

 

 本文由用戶 JosMcGovern 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!