###### tags: `PWA` # [7.Caching](https://web.dev/learn/pwa/caching/) 緩存 *You can use the Cache Storage API to download, store, delete or update assets on the device. Then these assets can be served on the device without needing a network request.* 您可以使用緩存存儲 API 下載、存儲、刪除或更新設備上的資產。然後,無需網絡請求即可在設備上提供這些資產。 Cache storage is a powerful tool. It makes your apps less dependent on network conditions. With good use of caches you can make your web app available offline and serve your assets as fast as possible in any network condition. As mentioned in [Assets and Data](https://web.dev/learn/pwa/assets-and-data/) you can decide the best strategy for caching the necessary assets. To manage the cache your service worker interacts with the [Cache Storage API](https://developer.mozilla.org/zh-CN/docs/Web/API/CacheStorage). 緩存存儲是一個強大的工具。它使您的應用減少對網絡條件的依賴。通過良好地使用緩存,您可以使您的 Web 應用程序離線可用,並在任何網絡條件下盡快為您的資產提供服務。如資產和數據中所述,您可以決定緩存必要資產的最佳策略。要管理緩存,您的 service worker 會與 Cache Storage API 交互。 ![](https://i.imgur.com/HvrBTjn.png) [Source](https://developer.mozilla.org/zh-CN/docs/Web/API/CacheStorage#browser_compatibility) <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;MARGIN-BOTTOM:1REM;color:#174ea6"> When installing platform-specific apps, the device stores the icon and other app assets in the operating system, in one step. For PWAs, the process has two separate steps. A PWA can store assets on your device any time after the first visit in the browser, even without installation. Install is a separate action that is covered later in this course. </div> <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;MARGIN-BOTTOM:1REM;color:#174ea6"> 安裝特定於平台的應用程序時,設備會一步將圖標和其他應用程序資產存儲在操作系統中。對於 PWA,該過程有兩個獨立的步驟。 PWA 可以在瀏覽器首次訪問後隨時將資產存儲在您的設備上,甚至無需安裝。安裝是一個單獨的操作,稍後將在本課程中介紹。 </div> The Cache Storage API is available from different contexts: 緩存存儲 API 可從不同的上下文中獲得: * The window context (your PWA's main thread). * The service worker. * Any other workers you use. * 窗口上下文(您的 PWA 的主線程)。 * 服務人員。 * 您使用的任何其他工人。 One advantage of managing your cache using service workers is that its lifecycle is not tied to the window, which means you are not blocking the main thread. Be aware that to use the Cache Storage API most of these contexts have to be under a TLS connection. ## What to cache 如何cache The first question you may have about caching is what to cache. While there is no single answer to that question, you can start with all the minimum resources that you need to render the user interface. 使用服務工作者管理緩存的一個優點是它的生命週期與窗口無關,這意味著您不會阻塞主線程。請注意,要使用緩存存儲 API,大多數這些上下文都必須處於 TLS 連接下。 Those resources should include:這些資源應包括: * The main page HTML (your app's start_url). * CSS stylesheets needed for the main user interface. * Images used in the user interface. * JavaScript files required to render the user interface. * Data, such as a JSON file, required to render a basic experience. * Web fonts. * On a multi-page application, other HTML documents that you want to serve fast or while offline. * 主頁 HTML(您應用的 start_url)。 * 主要用戶界面所需的 CSS 樣式表。 * 用戶界面中使用的圖像。 * 呈現用戶界面所需的 JavaScript 文件。 * 呈現基本體驗所需的數據,例如 JSON 文件。 * 網頁字體。 * 在多頁應用程序中,您希望快速或離線提供的其他 HTML 文檔。 <div style="padding:1rem;background-color:#fff5e3;margin-bottom:1em"> <div style="display:flex"> :warning: <p style="color:#c34900;margin-top:-1.5em"> Caution </p> </div> <p style="color:#c34900;"> Remember that you are downloading and storing assets on users' devices, so use that space and bandwidth responsibly. You need to find the balance between having enough on-device assets to render a fast or offline experience without consuming too much data. </p> </div> <div style="padding:1rem;background-color:#fff5e3;margin-bottom:1em"> <div style="display:flex"> :warning: <p style="color:#c34900;margin-top:-1.5em"> Caution </p> </div> <p style="color:#c34900;"> 請記住,您是在用戶設備上下載和存儲資產,因此請負責任地使用該空間和帶寬。您需要在擁有足夠的設備上資產以在不消耗太多數據的情況下呈現快速或離線體驗之間找到平衡點。 </p> </div> ## Offline-ready 離線準備 While being offline-capable is one of the requirements for a Progressive Web App, it's essential to understand that not every PWA needs a full offline experience, for example cloud gaming solutions or crypto assets apps. Therefore, it's OK to offer a basic user interface guiding your users through those situations. 雖然具有離線功能是漸進式 Web 應用程序的要求之一,但必須了解並非每個 PWA 都需要完整的離線體驗,例如雲遊戲解決方案或加密資產應用程序。因此,可以提供一個基本的用戶界面來指導您的用戶度過這些情況。 Your PWA should not render a browser's error message saying that the web rendering engine couldn't load the page. Instead use your service worker to show your own messaging, avoiding a generic and confusing browser error. 您的 PWA 不應呈現瀏覽器的錯誤消息,指出 Web 呈現引擎無法加載頁面。而是使用您的 service worker 來顯示您自己的消息,避免出現一般且令人困惑的瀏覽器錯誤。 <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> If you [publish your PWA to Google Play Store](https://chromeos.dev/en/publish/pwa-in-play), your PWA should never render an HTTP error message from the browser to avoid penalizations within the store listings. Check [Changes to Quality Criteria for PWAs](https://blog.chromium.org/2020/06/changes-to-quality-criteria-for-pwas.html) for more information. </p> </div> <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> 如果您將 PWA 發佈到 Google Play 商店,您的 PWA 不應從瀏覽器呈現 HTTP 錯誤消息,以避免在商店列表中受到懲罰。查看 PWA 質量標準的更改以獲取更多信息。 </p> </div> There are many different caching strategies you could use depending on the needs of your PWA. That's why it is important to design your cache usage to provide a fast and reliable experience. For example if all your app assets will download fast, don't consume a lot of space, and don't need to be updated in every request, caching all your assets would be a valid strategy. If on the other hand you have resources that need to be the latest version you might want to consider not caching those assets at all. 根據 PWA 的需要,您可以使用許多不同的緩存策略。這就是為什麼設計緩存使用以提供快速可靠的體驗很重要。例如,如果您的所有應用資產都將快速下載,不會佔用大量空間,並且不需要在每個請求中更新,那麼緩存您的所有資產將是一個有效的策略。另一方面,如果您擁有需要最新版本的資源,您可能需要考慮根本不緩存這些資產。 <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> The cache storage content and eviction rules are set per origin and not per PWA, since it's possible to have more than one in a single origin. If you share your origin for many PWAs, it's a good idea to add a prefix to your cache names to avoid collision problems between each PWA's data storage. </p> </div> <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> 緩存存儲內容和逐出規則是按源設置的,而不是按 PWA 設置的,因為在一個源中可能有多個。如果你為許多 PWA 共享你的來源,最好為你的緩存名稱添加一個前綴,以避免每個 PWA 的數據存儲之間出現衝突問題。 </p> </div> ## Using the API 使用 API Use the Cache Storage API to define a set of caches within your origin, each identified with a string name you can define. Access the API through the <kbd> caches </kbd> object, and the <kbd>open </kbd> method enables the creation, or opening of an already created cache. The open method returns a promise for the cache object. 使用緩存存儲 API 在您的源中定義一組緩存,每個緩存都使用您可以定義的字符串名稱進行標識。通過緩存對象訪問 API,open 方法支持創建或打開已創建的緩存。 open 方法返回緩存對象的承諾。 ``` caches.open("pwa-assets") .then(cache => { // you can download and store, delete or update resources with cache arguments }); ``` ## Downloading and storing assets 下載和存儲資產 To ask the browser to download and store the assets use the <kbd>add </kbd> or <kbd>addAll </kbd> methods. The <kbd>add </kbd> method makes a request and stores one HTTP response, and <kbd>addAll </kbd> a group of HTTP responses as a transaction based on an array of requests or URLs. 要要求瀏覽器下載和存儲資產,請使用 add 或 addAll 方法。 add 方法發出請求並存儲一個 HTTP 響應,addAll 一組 HTTP 響應作為基於請求或 URL 數組的事務。 ``` caches.open("pwa-assets") .then(cache => { cache.add("styles.css"); // it stores only one resource cache.addAll(["styles.css", "app.js"]); // it stores two resources }); ``` <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;color:#174ea6"> Both <kbd>add </kbd> and <kbd>addAll </kbd> return a promise with no arguments; if it's fulfilled, you know the assets were downloaded and cached, and if it fails, the API couldn't download one or more resources, and it didn't modify the cache. </div> <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;color:#174ea6;margin-bottom:1rem"> add 和 addAll 都返回一個沒有參數的承諾;如果它完成,你知道資產被下載和緩存,如果失敗,API 無法下載一個或多個資源,並且它沒有修改緩存。 </div> The cache storage interface stores the entirety of a response including all the headers and the body. Consequently, you can retrieve it later using an HTTP request or a URL as a key. You will see how to do that in [the Serving chapter](https://web.dev/learn/pwa/serving/). 緩存存儲接口存儲整個響應,包括所有標頭和正文。因此,您可以稍後使用 HTTP 請求或 URL 作為鍵來檢索它。您將在“服務”一章中看到如何做到這一點。 <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;color:#174ea6"> To download and store the assets, you must specify all the URLs explicitly. Otherwise the API cannot know all the assets you need or want to cache. </div> <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;color:#174ea6"> 要下載和存儲資產,您必須明確指定所有 URL。否則 API 無法知道您需要或想要緩存的所有資產。 </div> ## When to cache 何時緩存 In your PWA, you are in charge of deciding when to cache files. While one approach is to store as many assets as possible when the service worker is installed, it is usually not the best idea. Caching unnecessary resources wastes bandwidth and storage space and could cause your app to serve unintended outdated resources. 在您的 PWA 中,您負責決定何時緩存文件。雖然一種方法是在安裝 service worker 時存儲盡可能多的資產,但這通常不是最好的主意。緩存不必要的資源會浪費帶寬和存儲空間,並可能導致您的應用程序提供意外的過時資源。 You don't need to cache all the assets at once, you can cache assets many times during the lifecycle of your PWA, such as: 您不需要一次緩存所有資產,您可以在 PWA 的生命週期內多次緩存資產,例如: * On installation of the service worker. * After the first page load. * When the user navigates to a section or route. * When the network is idle. * 關於service worker的安裝。 * 第一頁加載後。 * 當用戶導航到某個部分或路線時。 * 當網絡空閒時。 You can request caching new files in the main thread or within the service worker context. 您可以請求在主線程或服務工作者上下文中緩存新文件。 ## Caching assets in a service worker 在服務工作者中緩存資產 One of the most common scenarios is to cache a minimum set of assets when the service worker is installed. To do that, you can use the cache storage interface within the <kbd>install </kbd> event in the service worker. 最常見的場景之一是在安裝 service worker 時緩存最少的資產集。為此,您可以在服務工作者的安裝事件中使用緩存存儲接口。 Because the service worker thread can be stopped at any time, you can request the browser to wait for the <kbd>addAll </kbd> promise to finish to increase the opportunity of storing all the assets and keeping the app consistent. The following example demonstrates how to do this, using the <kbd>waitUntil </kbd> method of the event argument received in the service worker event listener. 因為 service worker 線程可以隨時停止,您可以請求瀏覽器等待 addAll 承諾完成,以增加存儲所有資產並保持應用程序一致的機會。以下示例演示瞭如何使用服務工作者事件偵聽器中接收到的事件參數的 waitUntil 方法來執行此操作。 ``` const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"]; self.addEventListener("install", event => { event.waitUntil( caches.open("pwa-assets") .then(cache => { return cache.addAll(urlsToCache); }); ); }); ``` The <kbd> [waitUntil](https://developer.mozilla.org/zh-CN/docs/Web/API/ExtendableEvent/waitUntil) </kbd> method receives a promise and asks the browser to wait for the task in the promise to resolve (fulfilled or failed) before terminating the service worker process. You may need to chain promises and return the <kbd> add()</kbd> or <kbd>addAll </kbd> calls so that a single result gets to the <kbd>waitUntil() </kbd> method. <kbd>waitUntil() </kbd> 方法接收一個承諾,並要求瀏覽器在終止服務工作者進程之前等待承諾中的任務解決(完成或失敗)。您可能需要鏈接承諾並返回 <kbd> add()</kbd> 或 <kbd>addAll </kbd> 調用,以便單個結果到達 <kbd>waitUntil() </kbd>方法。 You can also handle promises using the async/await syntax. In that case, <kbd>waitUntil() </kbd> needs a promise-based function as an argument, so you need to create a function that returns the promise to make it work, as in the following example: 您還可以使用 async/await 語法處理承諾。在這種情況下, <kbd>waitUntil() </kbd>需要一個基於 promise 的函數作為參數,因此您需要創建一個返回 promise 的函數以使其工作,如以下示例所示: ``` const urlsToCache = ["/", "app.js", "styles.css", "logo.svg"]; self.addEventListener("install", (event) => { event.waitUntil(async () => { const cache = await caches.open("pwa-assets"); return cache.addAll(urlsToCache); }); }); ``` ## Cross-domain requests and opaque responses 跨域請求和不透明響應 Your PWA can download and cache assets from your origin and cross-domains, such as content from third-party CDNs. With a cross-domain app, the cache interaction is very similar to same-origin requests. The request is executed and a copy of the response is stored in your cache. As with other cached assets it is only available to be used in your app's origin. 您的 PWA 可以從您的來源和跨域下載和緩存資產,例如來自第三方 CDN 的內容。對於跨域應用程序,緩存交互與同源請求非常相似。執行請求並將響應的副本存儲在您的緩存中。與其他緩存資產一樣,它只能在您應用程序的來源中使用。 The asset will be stored as an [opaque response](https://fetch.spec.whatwg.org/#concept-filtered-response-opaque), which means your code won't be able to see or modify the contents or headers of that response. Also, opaque responses don't expose their actual size in the storage API, affecting quotas. Some browsers expose large sizes, such as 7Mb no matter if the file is just 1Kb. 資產將存儲為不透明的響應,這意味著您的代碼將無法查看或修改該響應的內容或標頭。此外,不透明響應不會在存儲 API 中公開其實際大小,從而影響配額。一些瀏覽器暴露大尺寸,例如 7Mb,無論文件是否只有 1Kb。 <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> Remember that when you cache opaque responses from cross-domains, <kbd> cache.add() </kbd> and <kbd>cache.addAll() </kbd> will fail if those responses don't return with a 2xx status code. Therefore, if one CDN or cross-domain fails, all the assets you are downloading will be discarded, even successful downloads in the same operation. </p> </div> <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> 請記住,當您緩存來自跨域的不透明響應時,如果這些響應未返回 2xx 狀態代碼,則 cache.add() 和 cache.addAll() 將失敗。因此,如果某個 CDN 或跨域失敗,您正在下載的所有資產都將被丟棄,即使是在同一操作中成功下載。 </p> </div> ## Updating and deleting assets 更新和刪除資產 You can update assets using <kbd> cache.put(request, response) </kbd> and delete assets with <kbd> delete(request).</kbd> 您可以使用 <kbd> cache.put(request, response) </kbd> 更新資產並使用 <kbd> delete(request).</kbd> 刪除資產。 <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> The Cache Storage API doesn't update your assets if you change them on your server nor does it delete them. Your code should manage both situations, and for that, there are different design patterns. You'll learn about a library to help with these situations in the Workbox chapter. </p> </div> <div style="padding:1rem;background-color:#fce8e8;margin-bottom:1rem;color:#a50e0e"> <div style="display:flex"> :warning: <p style="color:#a50e0e;margin-top:-1.5em"> Caution警告 </p> </div> <p style="color:#a50e0e;"> 如果您在服務器上更改資產,緩存存儲 API 不會更新您的資產,也不會刪除它們。您的代碼應該管理這兩種情況,為此,有不同的設計模式。您將在 Workbox 一章中了解一個庫來幫助解決這些情況。 </p> </div> Check the [Cache object documentation](https://developer.mozilla.org/zh-CN/docs/Web/API/Cache) for more details. 查看緩存對象文檔以獲取更多詳細信息。 ![](https://i.imgur.com/BBdwO45.png) [learn-pwa-asset-caching](https://glitch.com/~learn-pwa-asset-caching) ## Debugging Cache Storage 調試緩存存儲 Many browsers offer a way to debug the contents of cache storage within their DevTools Application tab. There, you can see the contents of every cache within the current origin. We'll cover more about these tools in the [Tools and Debug chapter](https://web.dev/learn/pwa/tools-and-debug/). ![](https://i.imgur.com/ra0584q.png) <div style="background-color:#deeafd;padding:1rem;line-height:1.9;margin-top:1rem;color:#174ea6"> Web Inspector on Safari on macOS doesn't have a way to see the contents of cache storage. For that purpose, you can use the free [Service Worker Detector Safari extension](https://apps.apple.com/app/service-worker-detector/id1530808337?l=en&mt=12) created by [Thomas Steiner](https://twitter.com/tomayac). 許多瀏覽器都提供了一種在 DevTools Application 選項卡中調試緩存存儲內容的方法。在那裡,您可以看到當前源中每個緩存的內容。我們將在“工具和調試”一章中詳細介紹這些工具。 </div> ## Resources * [Cache Storage on MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/CacheStorage) * [The Cache API: A quick guide](https://web.dev/cache-api-quick-guide/) * [The Offline Cookbook](https://web.dev/offline-cookbook/)