如何在Service Worker缓存Web应用资源时显示下载进度?
嘿,这个问题我太有共鸣了——当初我第一次用Service Worker做离线缓存时,也遇到过一次性缓存上百个文件导致浏览器卡成狗的情况,用户体验极差。不过咱们可以通过跟踪单个文件缓存进度+异步分批处理来解决,既显示清晰的进度提示,又避免浏览器无响应。
下面是我验证过的最佳方案,分步骤来:
核心思路
放弃cache.addAll()这种一次性批量缓存的方式,改成逐个(或小批量)遍历文件列表,缓存每个文件后立即向主线程发送进度更新。这样既能精确跟踪每个文件的缓存状态,又能避免瞬间发起大量网络请求导致的资源拥堵。
具体实现步骤
1. Service Worker 端:处理缓存并发送进度
在你的Service Worker文件(比如sw.js)里,我们手动遍历缓存列表,逐个缓存文件,每完成一个就给主线程发消息:
const CACHE_NAME = 'my-app-cache-v1'; // 替换成你的实际缓存文件列表 const FILES_TO_CACHE = [ '/', '/index.html', '/styles/main.css', '/js/app.js', // ... 剩下的96个文件 ]; self.addEventListener('install', async (event) => { // 可选:让新SW立即激活,不用等旧页面关闭 self.skipWaiting(); const cache = await caches.open(CACHE_NAME); const totalFiles = FILES_TO_CACHE.length; let completedCount = 0; // 逐个处理文件缓存,避免一次性发起大量请求 for (const fileUrl of FILES_TO_CACHE) { try { // 缓存单个文件 await cache.add(fileUrl); completedCount++; } catch (err) { // 容错:某个文件缓存失败不影响整体,记录错误即可 console.error(`缓存失败: ${fileUrl}`, err); completedCount++; } // 向所有打开的页面发送进度更新 const clients = await self.clients.matchAll({ type: 'window' }); clients.forEach(client => { client.postMessage({ type: 'CACHE_PROGRESS', data: { completed: completedCount, total: totalFiles } }); }); // 可选:加个微小延迟,避免瞬间占满网络请求队列 await new Promise(resolve => setTimeout(resolve, 30)); } }); // 激活时清理旧缓存,保持缓存版本干净 self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.filter(name => name !== CACHE_NAME).map(name => caches.delete(name)) ); }) ); });
2. 主线程(页面):监听进度并更新UI
在你的页面脚本里,监听Service Worker发来的消息,把进度显示到页面上:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(registration => { // 监听SW发送的进度消息 navigator.serviceWorker.addEventListener('message', (event) => { if (event.data.type === 'CACHE_PROGRESS') { const { completed, total } = event.data.data; // 更新页面上的进度元素(确保页面上有id为cache-progress的元素) const progressEl = document.getElementById('cache-progress'); if (progressEl) { if (completed === total) { progressEl.textContent = '✅ 缓存完成!现在可以离线使用啦'; } else { progressEl.textContent = `正在下载第${completed}个文件,共${total}个`; } } } }); // 如果页面刷新时SW已经在缓存,主动请求当前进度 if (registration.active) { registration.active.postMessage({ type: 'GET_CURRENT_PROGRESS' }); } }); }
3. 页面上的UI元素
在你的HTML里加个显示进度的容器:
<div id="cache-progress" style="padding: 1rem; background: #f5f5f5; text-align: center;"></div>
关键优化点(避免浏览器无响应)
- 逐个/分批缓存:避免一次性发起上百个网络请求,分散请求压力
- 微小延迟:每次缓存后加30-50ms的延迟,给浏览器喘息的机会
- 容错处理:单个文件缓存失败不中断整个流程,保证大部分资源能缓存成功
- 异步处理:全程用
async/await,不会阻塞Service Worker的事件循环
这样一来,用户就能看到清晰的下载进度,浏览器也不会因为瞬间处理大量请求而卡顿无响应,完美解决你的问题!
内容的提问来源于stack exchange,提问作者Kaarug
相关产品推荐
相关产品推荐

