如何使用Workbox缓存API响应实现断网时仍可展示历史数据
问题分析与解决方案
现有配置的问题
- 策略选择不合理:当前查询类API使用
CacheFirst策略,只要缓存存在就永远不会发起网络请求拉取最新数据,工单数据更新后用户无法获取到最新内容 - 未区分POST接口类型:现有配置中所有匹配
/\/api\/.*\/*.php/的POST请求都走NetworkOnly策略,完全不做缓存,如果你的工单查询接口是POST类型,断网时自然无法读取缓存 - 无缓存过期规则:缓存会永久存储,长期使用会出现数据严重滞后、占用存储空间过多的问题
改造实现方案
根据你「优先从缓存读取数据,断网可正常展示」的需求,推荐使用StaleWhileRevalidate策略:优先返回缓存数据给前端,同时后台静默发起网络请求更新缓存,兼顾加载速度和数据时效性。如果需要优先展示最新数据、网络失败再降级读缓存,可以换成NetworkFirst策略。
改造后的配置代码
import {precacheAndRoute} from 'workbox-precaching'; import {clientsClaim, skipWaiting} from 'workbox-core'; import {registerRoute} from 'workbox-routing'; import {CacheFirst, NetworkFirst, NetworkOnly, StaleWhileRevalidate} from 'workbox-strategies'; import {CacheableResponsePlugin} from "workbox-cacheable-response"; import {BackgroundSyncPlugin} from 'workbox-background-sync'; import {Queue} from 'workbox-background-sync'; // 新增缓存过期插件 import {ExpirationPlugin} from 'workbox-expiration'; declare const self: ServiceWorkerGlobalScope; skipWaiting(); clientsClaim(); const queue = new Queue('cola'); const bgSyncPlugin = new BackgroundSyncPlugin('api-cola', { onSync: async ({queue}) => { let entry; while ((entry = await queue.shiftRequest())) { try { let clone = entry.request.clone(); await fetch(clone); console.error("Replay successful for request", entry.request); } catch (error) { console.error("Replay failed for request", entry.request, error); // Put the entry back in the queue and re-throw the error: await queue.unshiftRequest(entry); throw error; } } console.log("Replay complete!"); } }); // 注意:这个POST路由仅用于提交类请求(如新建工单、上传数据等),不要包含查询类POST接口 registerRoute( /\/api\/.*\/submit\/.*\.php/, // 建议调整匹配规则,只匹配提交类POST接口,不要覆盖查询类POST new NetworkOnly({ plugins: [bgSyncPlugin] }), 'POST' ); // 处理GET类型的API查询请求 registerRoute( ({url}) => url.origin === 'https://xxx.xxx.com' && url.pathname.startsWith('/api/'), new StaleWhileRevalidate({ // 替换为NetworkFirst可实现优先拉取最新数据,失败再读缓存 cacheName: 'api-cache', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], // 去掉404,避免缓存错误响应 }), // 新增缓存过期规则,最多存30天,最多缓存100条接口数据 new ExpirationPlugin({ maxAgeSeconds: 30 * 24 * 60 * 60, maxEntries: 100, }) ] }), 'GET' // 显式指定匹配GET请求 ); // 如果有POST类型的查询接口,单独添加规则 registerRoute( ({url, request}) => url.origin === 'https://xxx.xxx.com' && url.pathname.startsWith('/api/workorder/list') && // 匹配工单查询接口路径 request.method === 'POST', new StaleWhileRevalidate({ cacheName: 'post-api-cache', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), new ExpirationPlugin({ maxAgeSeconds: 7 * 24 * 60 * 60, }) ] }) ); registerRoute( /assets\/images\/icons\/icon-.+\.png$/, new CacheFirst({ cacheName: 'icons' }) ); precacheAndRoute(self.__WB_MANIFEST);
额外注意事项
- 首次访问工单页面时需要有网络,接口返回成功后才会写入缓存,后续断网才能正常读取
- 如果需要支持首次断网访问也有兜底内容,可以在前端代码中加降级逻辑:接口请求失败时读取本地存储的历史数据展示
- 提交类POST接口不要设置缓存,避免重复提交或者读取到旧的提交状态
内容的提问来源于stack exchange,提问作者Rafa Alvarez-Ossorio Martin
相关产品推荐
相关产品推荐

