PWA 学习笔记之 Service Workers Cache Control
cn-programming·@semlinker·
0.000 HBDPWA 学习笔记之 Service Workers Cache Control
通过[PWA 学习笔记之 Service Workers](https://steemit.com/cn-programming/@semlinker/pwa-service-workers)这篇文章,我们了解了如何使用、调试、更新 Service Worker 及 Service Worker 生命周期相关知识。接下来本文将介绍如何利用 fetch API、CacheStorage API 及 Service Worker 实现缓存控制。 ### fetch 事件及自定义响应 在你使用缓存前,你必须能够拦截网络请求。强大的 Service Worker 为我们提供了这种能力,在 Service Worker 作用域内的每个网络请求,将会触发 fetch 事件: ```javascript self.addEventListener('fetch', (event) => { event.respondWith(fetch(event.request)); }); ``` 以上代码我们只是实现简单的请求代理,实际上并没有多大用途,其实我们是可以自定义响应对象,即通过 fetch API 定义的 Response 接口,创建响应对象,使用的语法如下: ```javascript let myResponse = new Response(body, init); ``` **参数说明**: - body(可选)—— 定义 response 中 body 内容,支持以下数据类型: - Blob - BufferSource - FormData - URLSearchParams - USVString - init(可选)—— 用于配置响应对象,支持以下属性: - status:响应的状态码,如 200 - statusText:与状态码关联的状态信息,如 OK - headers:响应头部对象 **使用示例**: ```javascript var myBlob = new Blob(); var init = { "status" : 200 , "statusText" : "Awesome!" }; var myResponse = new Response(myBlob,init); ``` 光写不练假把式,我们立马实践一下,手动更新 service-worker.js 文件: ```javascript self.addEventListener('install', (event) => { self.skipWaiting(); console.log('Update service worker installed', event); }); self.addEventListener('activate', (event) => { console.log('Update service worker activated', event); }); // 以下是新增的内容 self.addEventListener('fetch', (event) => { event.respondWith(new Response('My name is semlinker!')); }); ``` 更新完保存文件,然后在浏览器中访问 index.html 文件,此时你会在页面中看到以下内容: ``` My name is semlinker! ``` ### CacheStorage API 缓存是我们的好朋友,利用缓存我们甚至可以在离线状态下,使得 App 仍然可以使用。接下来我们继续更新 service-worker.js 文件,在 install 事件处理函数中,我们利用 CacheStorage API 对特定的资源进行缓存: ```javascript self.addEventListener('install', (event) => { self.skipWaiting(); if ('caches' in self) { event.waitUntil( caches.open('v1').then((cache) => { return cache.addAll([ './index.html', './script.js', './sw-in-action.png' ]); }) ); } console.log('Update service worker installed', event); }); // 注释掉自定义响应对象 self.addEventListener('fetch', (event) => { // event.respondWith(new Response('My name is semlinker!')); }); ``` 在 install 事件处理函数中,我们先判断当前浏览器是否支持 CacheStorage API,若当前浏览器支持该 API 的话,我们也可以通过 `window.caches` 访问 CacheStorage 对象。然后我们使用 `event.waitUntil` 方法让 Service Worker 处于 installing 阶段,直到设定的任务完成。这里需要注意的是,event.waitUntil 方法的参数类型必须为 Promise。 最后,我们调用 `caches.open('v1')` 方法来获取 v1 版本的缓存对象(若不存在,则会自动创建),进而通过调用缓存对象的 addAll 方法,缓存对应资源。 为了能够更直观的感受缓存特性,我们新增了一张图片资源。这时,我们需要注释掉 fetch 事件处理器中,自定义响应的代码。除此之外,我们也需要更新一下 index.html 文件,新增以下 img 标签: ```html <!-- <img src="sw-in-action.png"> --> ``` 万事俱备只欠东风,接下来重新刷新一下浏览器,不出所料的话,你将会看到一张图片。难道这样就结束了?当然不是,我们还没检查已设置的资源,是否被成功缓存。还是老样子,打开开发者工具,切换到 Application Tab 页,不过这次我们打开的是 Cache 菜单下的 CacheStorage 子菜单,具体内容如下图所示:  通过上图,可以发现我们设置的资源已经被成功缓存了。那么,接下来的事情就是应该如何利用缓存。不知道小伙伴们,是否还记得[PWA 学习笔记之 CacheStorage API](https://steemit.com/cn-programming/@semlinker/pwa-cachestorage-api)这篇文章中介绍过的 match 方法,**该方法返回一个 Promise 对象,用于判断给定的请求对象是否已被缓存。若匹配则返回已缓存的对象**。下面,我来继续更新 service-worker.js 文件,这里我们只需更新 fetch 事件处理器: ```javascript self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) ); }); ``` 重新保存 service-worker.js 文件,在成功激活新的 Service Worker 后,我们先切换到 Network Tab 页,然后再次刷新一下浏览器。具体的内容如下图所示:  从图中,我们可以看出当刷新当前页面时,Service Worker 直接为我们返回了之前已缓存的资源。另外,感兴趣的小伙伴,你也可以把网络状态设置为 Offline,或者进入 Application Tab 页,把 Cache Storage 中 v1 的缓存删掉,然后再刷新当前页面,看一下页面的显示情况。 其实 fetch 事件处理器的代码存在一个问题,假如我们哪天更新了 index.html 文件,比如再次新增一个 img 标签,它引用了新的图片资源,但并没有把新的图片资源保存到缓存对象中,这时会出现什么问题呢?我们立马来验证一下,具体流程如下: - 复制已有的 sw-in-action.png 文件,重命名为 sw-in-action-1.png; - 更新 index.html 文件,添加新的 img 标签并设置其 src 地址(引用刚创建的 sw-in-action-1.png 文件)。 保存文件,刷新当前页面,再次观察 Network Tab 页的内容,具体如下图所示:  图中我们发现已缓存的资源,能够正常返回,但由于我们没有把 sw-in-action-1.png 文件,添加到缓存中,使得该资源的请求,缓存匹配失败,最终导致 sw-in-action-1.png 该文件无法在页面中正常显示 。 那么如何解决这个问题呢?最简单的方案就是把 sw-in-action-1.png 文件添加到 cache.addAll 方法的数组中。这样虽然可以解决问题,但有没有更好的方案呢?**理想的情况下,我们优先从缓存中获取对应的资源,若缓存中不存在对应的资源,且当前网络是可用的情况下,我们可以在无法获取缓存的时候,发起网络请求,获取对应的资源**。按照以上的分析思路,我们再来更新一下 service-worker.js 文件,具体如下: ```javascript self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { // 若缓存命中,则直接返回缓存对象,否则通过fetch API从网络获取 return response || fetch(event.request); }) ); }); ``` 使用上面的代码,我们解决了刚才遇到的问题。那么上述的代码,还可以优化么?答案是可以的,我们可以把从网络获取的资源,添加到缓存对象中,以保证离线状态下,仍可以使用。要实现该功能,还是利用我们之前学过的 CacheStorage API,具体实现代码如下: ```javascript self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { return response || fetch(event.request).then((response) => { return caches.open('v1').then((cache) => { cache.put(event.request, response.clone()); return response; }); }); }) ); }); ``` 通过这篇文章我们把之前所学的知识,如 fetch API、CacheStorage API 及 Service Worker 等知识综合应用起来,简单实现了缓存控制。后面的文章我们将介绍不同的缓存策略及如何更新缓存。刚开始学习 PWA,以上内容有误之处,请小伙伴们多多指教。 ### 参考资源 - [MDN - Response](https://developer.mozilla.org/zh-CN/docs/Web/API/Response/Response)
👍 boopathy,