You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:36:01