Service Worker跳过等待无法激活当前正在等待的SW

5

描述:

我们正在使用 sw precache 来预先缓存脚本,因此为了更新脚本,我们提供了重新加载选项, 为此,我们正在监听工作线程消息以跳过等待新安装服务工作者的步骤,但出于未知原因,我们无法正确获取。

importScript

// GETTING OLD SW reference (self) and NOT getting newly installed SW reference

self.addEventListener('message', function(event) {
    *// not working*
    self.skipWaiting();
});



// But if we put skipWaiting() in 'install' listener 
// it is getting correct new SW reference and working correctly

self.addEventListener('install', function(event) {
    // self.skipWaiting();
});

软件注册

if('serviceWorker' in window.navigator) {
      window.addEventListener('load', function() {
        window.navigator.serviceWorker.register("/serviceWorker.js").then(function(registration) {
          console.log("ServiceWorker registration successful with scope: ", registration);
          registration.onupdatefound = function() {
            console.log('NEW WILD WORKER HAS SPAWNED.!', registration);
            var installedWorker = registration.installing;
            installedWorker.onstatechange = function() {
              if (installedWorker.state === 'installed') {
                if (navigator.serviceWorker.controller) {
                  console.log('Updated content is available RELOAD!', navigator.serviceWorker.controller);
                  var el = document.getElementById('feature');
                  el.style['display'] = 'block';
                }
              }
            }
          }
        }).catch(function(error) {
          console.error("ServiceWorker registration failed: ", error);
        });
      });
      window.navigator.serviceWorker.addEventListener('controllerchange', function() {
        console.log('SERVICE WORKER UPDATED');
      });
    }

Webpack配置

new SWPrecacheWebpackPlugin({
        cacheId: 'pwa',
        filename: 'serviceWorker.js',
        staticFileGlobsIgnorePatterns: [/\.map$/, /\.json$/, /_nch\.[0-9a-z]+\.[js, css]+/g, /webpackManifest\.[0-9a-z]+\.js/g, /.DS_Store\.[0-9a-z]+/g],
        importScripts: ['offline/offline.1a2b3c4df1.js'],
        dontCacheBustUrlsMatching: /./,
        minify: false,
        skipWaiting: false,
        runtimeCaching: [ {
          urlPattern: /_nch\.[0-9a-z]+\.[js, css]+/g,
          handler: 'fastest',
          options: {
            cache: {
              name: 'jd-internal-script',
              maxEntries: 10,
            },
          },
        }, {
          urlPattern: /webpackManifest\.[0-9a-z]+\.js/g,
          handler: 'networkFirst',
          options: {
            cache: {
              name: 'jd-root-doc',
            },
          },
        }],
      }),
2个回答

13

有关 skipWaiting() 的最佳文档可在 https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle#skip_the_waiting_phase 找到。

您可以在 install 处理程序中无条件地调用它,或者按照您似乎正在执行的模型,监听 message 事件并有条件地调用 skipWaiting()

如果您走的是有条件的路线,那么您应该修改客户端页面的代码以正确检测注册的服务工作者何时进入 waiting 状态,并为用户提供与页面交互的选项,以使相应的 postMessage() 告诉服务工作者跳过等待。 根据您的说法,您已经尝试了这样做,但看起来您正在将消息发送到错误的服务工作者实例。

以下是您的页面代码应该如何:

// On your page:
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('service-worker.js').then(function(reg) {
      reg.onupdatefound = function() {
        var newSW = reg.installing;
        newSW.onstatechange = function() {
          if (newSW.state === 'waiting') {
            // This assumes there's a button with id='skip-waiting-button' that
            // users should click to get the new SW to activate immediately.
            var button = document.querySelector('#skip-waiting-button');
            button.addEventListener('click', function() {
              newSW.postMessage('skipWaiting');
            });
            // Assume that 'display' is 'none' initially.
            button.style.display = 'inline';
          }
          // Handle whatever other SW states you care about, like 'active'.
        };
      };
    })
  });
}

// In your service worker:
self.addEventListener('message', event => {
  if (event.data === 'skipWaiting') {
    self.skipWaiting();
  }
});

感谢您让这个工作成功。我能否转向使用Workbox来实现服务工作者,因为目前我正在使用sw-precache和sw-toolbox来实现这一点。如果您能提供有关使用webpack实现Workbox的一些资源,那将非常有帮助。 - Anurag Singh
安装Worker.onstatechange时出现错误,提示installingWorker未定义,因此需要使用newSW来替换它。 - Anurag Singh
1
newSW.state = 'waiting' 这并不满足我们的需求,相反我们得到了 newSW.state = 'installed'。因此我们无法触发等待状态。 - Anurag Singh
1
“等待”状态在 TypeScript 的定义中找不到: type ServiceWorkerState = "installing" | "installed" | "activating" | "activated" | "redundant"; 它真的存在吗? - Alex C
1
@AlexC 你解决了这个问题吗? - itaintme
显示剩余5条评论

0

所以这是我的想法... 你的新服务工作者仍在等待.. 因此事件监听器仍在旧的服务工作者上.. 你发送的任何消息仍将被旧的服务工作者捕获(因为新的服务工作者正在等待)。

如果你只是想让新的服务工作者起作用,你可以简单地刷新页面而不是发送消息。


但是如果我这样做,那么缓存优先策略就不会起作用,因为由于Express生成的动态文档,webpackManifest和doc在运行时被缓存。在这种情况下,我必须切换到网络优先策略。 - Anurag Singh
我们在这里谈论的是捆绑包还是API,或者确切地说是什么? - prateekbh
这只涉及到bundles。当客户端请求文档时,服务器会发送一个文档,然后解析文档并下载服务工作者,安装并激活服务工作者。之后,它会预缓存那些资源(供应商等)。现在,如果我对我的脚本进行了一些更改并部署了我的新版本。那么下一次客户端访问服务器时,它会发现服务工作者文件已更改,并且会下载和安装相同的文件并进入等待状态,因此现在我会显示一个弹出消息,当用户点击时,我调用self.skipwaiting并重新加载页面。 - Anurag Singh
如果您不调用skipWaiting而只是重新加载页面会发生什么? - prateekbh
如果我重新加载页面,旧的 Service Worker 仍将控制页面,直到我导航到不同的 URL 或进行硬刷新,这样才能让新的 Service Worker 接管页面。 - Anurag Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接