PWA(Progressive Web App)入門系列:Cache Storage & Cache

快樂平方發表於2019-02-21

前言

目前瀏覽器的儲存機制有很多,如:indexedDB、localStorage、sessionStorage、File System API、applicationCache 等等,那為什麼又制定了一套 Cache API 呢?對比其他儲存機制有什麼優勢?

簡介

Cache API 是一套搭配 PWA serviceworker 賦能的儲存機制,來實現請求資料離線功能。與 applicationCache 相似,提供了力度極細的儲存控制能力,內容完全由指令碼控制。常在 serviceworker 中搭配 Fetch 使用,且同一個 URL 不同 header 可以儲存多個 Response。不提供跨域共享,且與HTTP快取完全隔離。

與其他儲存機制的區別:

  • Cache API 是非同步化的儲存方式,serviceworker中必須使用非同步化儲存。
  • Cache API 是以 Requst 做為 key,Response 做為 value 進行儲存的,非同步化的 IndexDB 基於結構化克隆儲存, 無法儲存流式資料,轉換成本過高,增大記憶體使用及影響速度。

CacheStorage 方法

CacheStorage 是 Cache 物件儲存的介面,可以通過兩種方式獲取:

ServiceWorkerGlobalScope.caches

Window.caches
複製程式碼

注: 必須在 https 環境下才能使用。

下面都是在 ServiceWorkerGlobalScope 環境下。

open

語法:

// cachs 是 CacheStorage 例項的只讀全域性變數
caches.open(cacheName).then(cache => {
  // 處理開啟的 cache 例項相關操作
});
複製程式碼

開啟(如果沒有 cacheName,則建立)為 cacheName 的 Cache 例項。

返回 Promise,resolve 為Cache 例項。

delete

語法:

caches.delete(cacheName).then(boolean => {
  // true: cache 發現並已經刪除
});
複製程式碼

查詢匹配 cacheName 的 Cache 物件。找到則刪除 Cache 物件並返回一個 resolve 為 true 的 Promise;沒找到 Cache 物件,則返回 false。

keys

語法:

caches.keys().then(keyList => {
  //對 keyList 做操作
});
複製程式碼

返回 Promise。包含 CacheStorage 下所有的 Cache 物件名稱字串的陣列。

has

語法:

caches.has(cacheName).then(boolean => {
  // true: cacheName 快取存在
});
複製程式碼

返回一個 Promise 物件,快取存在時 resolve 的布林值為 true 否則為 false。

match

語法:

caches.match(request[, options]).then(response => {
  // response 操作
  // 如果未匹配到,則 resolve 返回 undefined
});
複製程式碼

引數:

request: 要匹配的 Request。可以是 Request 物件或 URL 字串。

options:(可選) 可選。用於控制如何進行匹配操作。

  • ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
  • ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
  • ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)
  • cacheName:String。表示所要搜尋的快取名稱。如果不設定則全域性搜尋,查詢到第一個,立即返回。

Cache 方法

Cache 是 CacheStorage 的儲存實現,以 Request 做為 key,以 Response 做為 value 來進行儲存。可以通過 CacheStorage.open(cacheName) 開啟 Cache 來進行操作。

Cache 資料生成後,將會一直存在,修改/刪除 需要通過指令碼自己去實現。

注: Cache.add/Cache.put/Cache.addAll 只能在 request method 為 GET 的情況下使用。並且相同的 request key 下的 cache,在這三個方法下會被覆蓋。

put

通過指定的 request 和 response 新增到 cache 中。

response 的 status 可以是任意型別。

語法:

cache.put(request, response).then(() => {
  // 將 request/response 鍵值對新增到cache中
});
複製程式碼

引數:

  • request:你想新增的 Request
  • response:你想新增匹配 Request 的 Response

注: request 引數,method 只支援 GET,否則會出現「TypeError: Request method POST is unsupported」 錯誤。

delete

刪除匹配 request key 的 cache ,找到並刪除成功 resolve(true)。

語法:

cache.delete(request,{options}).then(boolean => {
  // true: 你的 cache 已經刪除
});
複製程式碼

引數:

request: 請求刪除的 Request。 options:(可選) 控制刪除搜尋 key 如何去匹配(同 match 方法)。

  • ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
  • ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
  • ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)

add

給定 request 引數,自動請求獲取 response,並填入 cache 物件中。 等於 fetch + cache.put

注: response status 為 opaque 的不能通過 add 方法新增,返回 reject。

語法:

cache.add(request).then(() => {
  // request 已經新增到 cache
});
複製程式碼

addAll

和 Cache.add 作用相同,引數為 request 的陣列。

注: 只有在所有 requests 都成功的情況下,才能完成 cache 快取。

語法:

cache.addAll(requests[]).then(() => {
  // 所有 requests 都新增到 cache 。
});
複製程式碼

match

返回匹配 request key 的第一個 cache。

注: 即使沒有匹配到,也將返回 resolve,只是值為 undefined。

語法:

cache.match(request, {options}).then(response => {
  // 對 response 做一些處理
});
複製程式碼

引數:

request: 請求匹配的 Request。 options:(可選) 控制搜尋 key 如何去匹配(同 match 方法)。

  • ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
  • ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
  • ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)

matchAll

作用同 Cache.match,區別在於 Cache.match 返回匹配的 responses[0],而 Cache.matchAll 返回所有匹配的 responses 陣列。

語法:

cache.matchAll(request,{options}).then(responses => {
  // 對 responses 陣列做一些處理
});
複製程式碼

引數:

request: 請求匹配的 Request。 options:(可選) 控制搜尋 key 如何去匹配(同 match 方法)。

  • ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
  • ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
  • ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)

keys

返回當前 cache 例項下所有的 key。

注: 具有相同URL但不同請求頭的請求,如果它們的響應頭中有 VARY 頭部,則他們可以被返回。

語法:

cache.keys(request,{options}).then(keys => {
  // 對 requests 做一些處理
});
複製程式碼

引數:

request:(可選) 如果一個相關 Request key 被指定,則返回對應的 Request。 options:(可選) 控制搜尋 key 如何去匹配(同 match 方法)。

  • ignoreSearch:Boolean,false。匹配時,是否忽略 url 的查詢引數。
  • ignoreMethod:Boolean,false。true 時忽略請求方法(GET/HEAD)匹配。
  • ignoreVary:Boolean,false。true 時忽略 Vary 頭匹配。(什麼是Vary)

除錯檢視

可以通過 Chrome 的 DevTools進行檢視。

Application → Cache → Cache Storage

Cache Storage 中是以 caches.open 建立的 cacheName 的 cache,右側列表中是通過 put/add/addAll 新增的 request key,點選可以在下方檢視相關的 request 和 response。

PWA(Progressive Web App)入門系列:Cache Storage & Cache

快取空間問題

web 端的離線儲存方式有三種,分別是:

  • Temporary
  • Persistent
  • Unlimited

而 Cache 屬於 Temporary 型別。Temporary 儲存是一種臨時儲存,任何Web應用程式都可以在沒有前期配額請求或使用者提示的情況下使用,但儲存的資料可以被瀏覽器隨時刪掉(佔用空間過多時,自動清理)。可以類比於檔案系統的 / tmp 目錄。

在 Chrome 和 Opera 中可以使用新的實驗性 API 向裝置請求持久化存取許可權:

navigator.storage.persist().then(isGranted => {
	// true : 授權
})
複製程式碼

各瀏覽器的離線空間:

  • Chrome <6% of free space
  • Firefox <10% of free space
  • Safari <50MB
  • IE10 <250MB
  • Edge Depenent on volume size

溢位處理策略:

  • Chrome LRU once Chrome runs out of sapce
  • Firefox LRU if the whole disk gets full
  • Safari No eviction
  • Edge No eviction

實際資料可通過 Quota Management API 來檢視。

常見問題

跨域快取

對於跨域快取,跨域的資源需要開啟 Access-Control-Allow-Origin 頭部,並且 Request 的 mode 需要設定為 cors。

const req = new Request("https://cross.com", { mode: "cors" });

fetch(req).then(response => {
  caches.open("cacheName").then(cache => {
    cache.put(req, res);
  });
});
複製程式碼

如果跨域資源沒有開啟 Access-Control-Allow-Origin 頭部,則需要把 mode 設定為 no-cors,但會導致 response 的 status 為 0 的 opaque 響應,後面會講。

POST 快取

因 Cache 中的規範指出只能儲存 GET 型別的 Request,所以想快取 POST 型別的 Request 怎麼辦呢?

這裡建議兩種方式:

  1. 將 Response 進行序列化,或者將實際資料提取,放到 IndexDB 中進行儲存,監聽 fetch 事件的時候,匹配 IndexDB 來查詢響應,並生成 Response 例項。
  2. 一些特殊的 POST 請求,如內容都是固化好的引數,通常不會發生變化,並且 URL 中有唯一性的標誌,則可以在 fetch 中監聽到這種特殊 POST 時,把其轉化為 GET 的 Request 來做為 cache 的 key,等 Response 收到後,把 POST 的 Response 做為轉化出來的 GET key 的 value 來儲存,即可實現。

opaque 不透明響應快取

針對 response 的 status 為 0 的 opaque 響應資源,也是可以儲存到 cache 中的,但是並不建議儲存。因為響應狀態是 0,並不能確認資源是否完整及正確,快取下來後,在 cache 中也是無法檢視其長度大小的,並且會導致一些儲存問題。

PWA(Progressive Web App)入門系列:Cache Storage & Cache
並且,突然間 Cache Storage 變成了 10.3MB。
PWA(Progressive Web App)入門系列:Cache Storage & Cache
但儲存的這個 opaque 狀態的響應實際只有幾 kb,但快取起來卻達到好幾 MB 這是什麼原因呢?

其實這是 Chrome 瀏覽器層為防止出現安全問題,所以把所有 opaque 狀態的請求都以這種幾 MB 的方式進行填充來保證安全的。

所以針對 opaque 狀態的請求,建議:

  • 不進行 opaque 狀態型別的快取。
  • 對 opaque 狀態類弄的快取新增 Access-Control-Allow-Origin 頭來實現狀態透明。

瀏覽器相容性

PWA(Progressive Web App)入門系列:Cache Storage & Cache


部落格名稱:王樂平部落格

CSDN部落格地址:blog.csdn.net/lecepin

知識共享許可協議
本作品採用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。

相關文章