Service Worker 全面進階

騰訊雲技術社區-掘金主頁持續爲你們呈現雲計算技術文章,歡迎你們關注!javascript


做者:villainthrcss

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

hehe

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

前方高能,若是以爲生活沒有趣味能夠繼續看下去,會讓你的人生更沒有趣味。若是以爲湊合能過,請 ctrl/command + wjava

繼續~node

Service Worker 講道理是由兩部分構成,一部分是 cache,還有一部分則是 Worker。因此,SW(Service Worker) 自己的執行,就徹底不會阻礙當前 js 進程的執行,確保性能第一。那 SW 究竟是怎麼工做的呢?git

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

咱們先來看看 SW 比較坑的地方,它的 lifecyclegithub

SW 的生命週期

首先,SW 並非你網頁加載就與生俱來的。若是,你須要使用 SW,你首先須要註冊一個 SW,讓瀏覽器爲你的網頁分配一塊內存空間來。而且,你可否註冊成功,還須要看你緩存的資源量決定(有可能失敗,真的有可能)。若是,你須要緩存的靜態資源所有保存成功,那麼恭喜您,SW 安裝成功。若是,其中有一個資源下載失敗而且沒法緩存,那麼此次吊起就是失敗的。不過,SW 是由重試機制的,這點也不算特別坑。web

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

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

google_fc

從入門到放棄

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

compa

基本上手機端是能用的。

基於 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

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 進行捕獲。整個流圖以下:

up_load

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;
          }
        );
      })
    );
});複製代碼

那麼整個流圖變爲:

fetch

而裏面最關鍵的地方就是 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

若是上述步驟成功後,原來的 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-v1page-v1。則,咱們一是須要先在 install 事件裏面將 ajajx-v1page-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 並不會直接進行替換,他會等到用戶下一次刷新頁面事後,使用新的緩存文件。

fileUpload

不過,這裏請注意,我並無說,咱們更新緩存只能在 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 只是用更新文件而已。咱們可使用一個流圖進行表示:

prefetch

ok,關於文件的緩存咱們就介紹到這裏。

用戶更新

如今,爲了更好的用戶體驗,咱們能夠作的更尊重用戶一些。能夠設置一個 button,告訴用戶是否選擇緩存指定文件。有同窗可能會想到使用 postmessage API,來告訴 SW 執行相關的緩存信息。不過事實上,還有更簡單的辦法來完成,即,直接使用 caches 對象。caches 和 web worker 相似。都是直接掛載到 window 對象上的。因此,咱們能夠直接使用 caches 這個全局變量來進行搜索。那麼該環節就不須要直接經過 SW,這個流程圖能夠畫爲:

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
上面在文件更新裏面也講了幾個更新的方式。簡單來講:

desk

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

網絡緩存同時幹

這種情形通常是用來裝逼的,一方面檢查請求,一方面有檢查緩存,而後看兩個誰快,就用誰,我這裏直接上代碼吧:

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,具體咱們能夠在控制檯中查看:

cacheObject

而且 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 的全部內容。

這裏放一張本身寫的總結圖吧:

Service+Worker.svg-44kB

原文連接:ivweb.io/topic/5876d…

相關推薦
React 同構思想
Vue.js先後端同構方案之準備篇——代碼優化
Vue組件開發實踐之scopedSlot的傳遞


此文已由做者受權騰訊雲技術社區發佈,轉載請註明文章出處
原文連接:www.qcloud.com/community/a…
獲取更多騰訊海量技術實踐乾貨,歡迎你們前往騰訊雲技術社區

相關文章
相關標籤/搜索