服务工作者检测离线状态的最佳实践

37

我有一个服务工作者,目的是将一个名为offline.html的页面缓存起来,如果客户端没有网络连接,则显示该页面。然而,有时它会错误地认为导航器处于离线状态,即navigator.onLine === false,即使它实际上并不是离线状态。这意味着用户可能会在在线状态下得到offline.html而不是实际内容,这显然是我想避免的。

这是我在main.js中注册服务工作者的方式:

// Install service worker for offline use and caching
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js', {scope: '/'});
}

我目前的service-worker.js文件:

const OFFLINE_URL = '/mysite/offline';
const CACHE_NAME = 'mysite-static-v1';

self.addEventListener('install', (event) => {
  event.waitUntil(
    // Cache the offline page when installing the service worker
    fetch(OFFLINE_URL, { credentials: 'include' }).then(response =>
      caches.open(CACHE_NAME).then(cache => cache.put(OFFLINE_URL, response)),
    ),
  );
});

self.addEventListener('fetch', (event) => {
  const requestURL = new URL(event.request.url);

  if (requestURL.origin === location.origin) {
    // Load static assets from cache if network is down
    if (/\.(css|js|woff|woff2|ttf|eot|svg)$/.test(requestURL.pathname)) {
      event.respondWith(
        caches.open(CACHE_NAME).then(cache =>
          caches.match(event.request).then((result) => {
            if (navigator.onLine === false) {
              // We are offline so return the cached version immediately, null or not.
              return result;
            }
            // We are online so let's run the request to make sure our content
            // is up-to-date.
            return fetch(event.request).then((response) => {
              // Save the result to cache for later use.
              cache.put(event.request, response.clone());
              return response;
            });
          }),
        ),
      );
      return;
    }
  }

  if (event.request.mode === 'navigate' && navigator.onLine === false) {
    // Uh-oh, we navigated to a page while offline. Let's show our default page.
    event.respondWith(caches.match(OFFLINE_URL));
    return;
  }

  // Passthrough for everything else
  event.respondWith(fetch(event.request));
});

我做错了什么?


你尝试过使用ononline和onoffline吗?这可能会帮助你找到问题的根源...但从我们所看到的情况来看,我建议你使用另一种机制来确定是否提供离线版本。其中一个原因是,当用户在线时,并不意味着你的服务器可达/在线。 - Salketer
1
可能与此相关 -> https://dev59.com/tmYq5IYBdhLWcg3wrihD 如果是这样,看起来最好的方法是一个后台任务,进行间歇性的ajax调用或类似操作,甚至更好的选择可能是使用一个websocket到您的服务器,这样比使用 navigator.onLine 更可靠。 - Keith
@Keith,虽然问题相反,但可能与之相关。我会检查一下是否使用ajax请求能解决问题。 - Kaivosukeltaja
1个回答

45

navigator.onLine和相关事件在您想要更新UI以指示您处于离线状态时非常有用,例如仅显示缓存中存在的内容。

但是,我建议避免编写依赖于检查navigator.onLine的服务工作者逻辑。相反,尝试无条件地进行fetch(),如果失败,请提供备用响应。这将确保您的Web应用程序在fetch()因为离线、lie-fi或您的Web服务器出现问题而失败时都能按预期行事。

// Other fetch handler code...

if (event.request.mode === 'navigate') {
  return event.respondWith(
    fetch(event.request).catch(() => caches.match(OFFLINE_URL))
  );
}

// Other fetch handler code...

这是一个不错的方法,但似乎也会在错误403时触发离线页面,从而破坏我们的登录流程。 - Kaivosukeltaja
9
只要返回某种响应,即使响应具有非 200 错误代码,fetch() 不会被拒绝。尝试在 JS 控制台中运行 fetch('https://httpbin.org/status/403').then(response => console.log('resolved', response)).catch(error => console.error('rejected', error)); 并查看它记录了什么。 - Jeff Posnick
看起来你是对的,还有其他原因导致工作线程提供了错误的内容。我需要进一步调查,但由于你的解决方案似乎是正确的,所以我将其标记为已接受。谢谢! - Kaivosukeltaja
1
嗨,韦斯顿——这是有意为之的。服务工作者大部分时间都不在运行,而你提出的建议需要每次设备上线/离线时有效地“唤醒”它才能触发该事件。像fetchmessage这样的事件可以唤醒它,但仅限于此。 - Jeff Posnick
@WestonRuter 我对此并不了解,但我的理解是 sync 事件(并非所有浏览器都支持)基本上作为“应用程序上线”事件的功能。 - MalcolmOcean
显示剩余4条评论

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