使用服务辅助角色管理网络请求

服务辅助角色是一种特殊类型的 Web 辅助角色,能够使用 Fetch API 截获、修改和响应网络请求。 若要存储资源,服务辅助角色可以访问 Cache API,并且可以访问异步客户端数据存储,例如 IndexedDB

服务辅助角色可以通过在本地缓存资源来加快 PWA 的速度,还可以通过使 PWA 即使在用户设备处于脱机状态或具有间歇性网络连接时也能使 PWA 更加可靠。

如果 PWA 具有服务辅助角色,则用户首次访问 PWA 时将安装服务辅助角色。 然后,服务辅助角色会与应用并行运行,即使应用未运行,也可以继续执行工作。

服务辅助角色负责截获、修改和响应网络请求。 当应用尝试从服务器加载资源时,或者当应用发送从服务器获取数据的请求时,可能会向服务辅助角色发出警报。 发生这种情况时,服务辅助角色可以决定让请求转到服务器,或者截获请求并从缓存返回响应。

显示应用与网络和缓存存储之间的服务辅助角色的关系图

注册服务辅助角色

与其他 Web 辅助角色类似,服务辅助角色必须存在于单独的文件中。 该文件包含一个服务辅助角色。 注册服务辅助角色时引用此文件,如以下代码所示:

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/serviceworker.js");
}

运行 PWA 的 Web 浏览器可以为服务辅助角色提供不同级别的支持。 此外,运行 PWA 的上下文可能不安全。 因此,在运行任何与服务辅助角色相关的代码之前,最好先测试对象是否存在 navigator.serviceWorker 。 在上述代码中,使用 serviceworker.js 位于站点根目录的文件注册服务辅助角色。

请确保将服务辅助角色文件放在你希望服务辅助角色管理的最高级别目录中。 此类目录称为服务 辅助角色的范围 。 在前面的代码中,文件存储在应用的根目录中,服务辅助角色管理应用域名下的所有页面。

如果服务辅助角色文件存储在目录中 js ,则服务辅助角色的范围将限制为 js 目录和任何子目录。 最佳做法是将服务辅助角色文件放在应用的根目录中,除非需要缩小服务辅助角色的范围。

截获请求

在服务辅助角色中使用的主事件是 fetch 事件。 每当应用运行的浏览器尝试访问服务辅助角色范围内的内容时,该 fetch 事件都会运行。

以下代码演示如何为 fetch 事件添加侦听器:

self.addEventListener("fetch", event => {
  console.log("Fetching", event.request);
});

在处理程序中 fetch ,可以控制请求是否转到网络、从缓存中拉取等。 根据请求的资源类型、更新频率以及应用程序特有的其他业务逻辑,你采用的方法可能会有所不同。

下面是在处理程序中可以执行的操作的 fetch 几个示例:

  • 如果可用,请从缓存返回响应;否则,请通过网络请求资源。
  • 从网络提取资源、缓存副本并返回响应。
  • 允许用户指定保存数据的首选项。
  • 为某些图像请求提供占位符图像。
  • 直接在服务辅助角色中生成响应。

服务辅助角色生命周期

服务辅助角色的生命周期由多个步骤组成,每个步骤触发一个事件。 可以将侦听器添加到这些事件,以运行代码来执行操作。 以下列表显示了服务辅助角色的生命周期和相关事件的高级视图:

  1. 注册服务辅助角色。

  2. 浏览器下载 JavaScript 文件,安装服务辅助角色,并触发 install 事件。 可以使用 install 事件预先缓存任何重要的长期文件 (,例如 CSS 文件、JavaScript 文件、徽标图像或从应用) 的脱机页面。

    self.addEventListener("install", event => {
      console.log("Install event in progress.");
    });
    
  3. 激活服务辅助角色,这会触发事件 activate 。 使用此事件清理过时的缓存。

    self.addEventListener("activate", event => {
      console.log("Activate event in progress.");
    });
    
  4. 当页面刷新或用户转到网站上的新页面时,服务辅助角色已准备好运行。 如果要在不等待的情况下运行服务辅助角色,请在事件期间install使用 self.skipWaiting() 方法,如下所示:

    self.addEventListener("install", event => {
      self.skipWaiting();
    });
    
  5. 服务辅助角色现在正在运行,可以侦 fetch 听事件。

预缓存资源

当用户首次访问你的应用时,如果你定义了服务辅助角色,则会安装应用的服务辅助角色。 install使用服务辅助角色代码中的 事件来检测何时发生这种情况,并缓存应用所需的所有静态资源。 缓存应用的静态资源 ((如起始页所需的 HTML、CSS 和 JavaScript 代码) ),即使用户的设备处于脱机状态,应用也能运行。

若要缓存应用的资源,请使用全局 caches 对象和 cache.addAll 方法,如下所示:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

请注意,在初始安装之后, install 事件不会再次运行。 若要更新服务辅助角色的代码,请参阅 更新服务辅助角色

现在, fetch 可以使用 事件从缓存返回静态资源,而不是从网络再次加载它们:

self.addEventListener("fetch", event => {
  async function returnCachedResource() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Find the response that was pre-cached during the `install` event.
    const cachedResponse = await cache.match(event.request.url);

    if (cachedResponse) {
      // Return the resource.
      return cachedResponse;
    } else {
      // The resource wasn't found in the cache, so fetch it from the network.
      const fetchResponse = await fetch(event.request.url);
      // Put the response in cache.
      cache.put(event.request.url, fetchResponse.clone());
      // And return the response.
      return fetchResponse.
    }
  }

  event.respondWith(returnCachedResource());
});

为简洁起见,上述代码示例不处理从网络获取请求 URL 失败的情况。

使用自定义脱机页

当应用使用多个 HTML 页面时,常见的脱机方案是在用户设备脱机时将页面导航请求重定向到自定义错误页:

// The name of the cache your app uses.
const CACHE_NAME = "my-app-cache";
// The list of static files your app needs to start.
// Note the offline page in this list.
const PRE_CACHED_RESOURCES = ["/", "styles.css", "app.js", "/offline"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache all static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

self.addEventListener("fetch", event => {
  async function navigateOrDisplayOfflinePage() {
    try {
      // Try to load the page from the network.
      const networkResponse = await fetch(event.request);
      return networkResponse;
    } catch (error) {
      // The network call failed, the device is offline.
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match("/offline");
      return cachedResponse;
    }
  }

  // Only call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === 'navigate') {
    event.respondWith(navigateOrDisplayOfflinePage());
  }
});

更新服务辅助角色

安装新的服务辅助角色版本

如果对服务辅助角色代码进行更改并将新的服务辅助角色文件部署到 Web 服务器,则用户的设备将逐渐开始使用新的服务辅助角色。

每当用户导航到应用的某个页面时,运行应用的浏览器都会检查服务器上是否有新版本的服务辅助角色。 浏览器通过比较现有服务辅助角色和新服务辅助角色之间的内容来检测新版本。 检测到更改后,将安装新的服务辅助角色 () 触发其 install 事件,然后新的服务辅助角色将等待现有服务辅助角色停止在设备上使用。

实际上,这意味着可以同时运行两个服务辅助角色,但只有现有的 (原始) 服务辅助角色会截获应用的网络请求。 当应用关闭时,现有服务辅助角色将停止使用。 下次打开应用时,会激活新的服务辅助角色。 将 activate 触发事件,新的服务辅助角色开始截获 fetch 事件。

可以通过在服务辅助角色的事件处理程序中使用,在安装 self.skipWaiting() 新服务辅助角色 install 后立即强制激活它。

若要详细了解如何更新服务辅助角色,请参阅更新 web.dev 上的 服务辅助角色

更新缓存的静态文件

预缓存静态资源(如 CSS 样式表文件)时(如 预缓存资源中所述),应用仅使用文件的缓存版本,不会尝试下载新版本。

若要确保用户获取对应用使用的静态资源的最新更改,请使用缓存破坏命名约定并更新服务辅助角色代码。

缓存破坏 意味着每个静态文件根据其版本命名。 这可以通过各种方式实现,但通常涉及使用生成工具,该工具读取文件的内容并根据内容生成唯一 ID。 该 ID 可用于命名缓存的静态文件。

接下来,更新服务辅助角色代码以在 期间 install缓存新的静态资源:

// The name of the new cache your app uses.
const CACHE_NAME = "my-app-cache-v2";
// The list of static files your app needs to start.
const PRE_CACHED_RESOURCES = ["/", "styles-124656.css", "app-576391.js"];

// Listen to the `install` event.
self.addEventListener("install", event => {
  async function preCacheResources() {
    // Open the app's cache.
    const cache = await caches.open(CACHE_NAME);
    // Cache the new static resources.
    cache.addAll(PRE_CACHED_RESOURCES);
  }

  event.waitUntil(preCacheResources());
});

// Listen to the `activate` event to clear old caches.
self.addEventListener("activate", event => {
  async function deleteOldCaches() {
    // List all caches by their names.
    const names = await caches.keys();
    await Promise.all(names.map(name => {
      if (name !== CACHE_NAME) {
        // If a cache's name is the current name, delete it.
        return caches.delete(name);
      }
    }));
  }

  event.waitUntil(deleteOldCaches());
});

比较上述代码片段与预缓存资源中的 和 值。CACHE_NAMEPRE_CACHED_RESOURCES 安装此新的服务辅助角色后,将创建新的缓存,并下载并缓存新的静态资源。 激活服务辅助角色后,将删除旧缓存。 此时,用户将拥有新版本的应用。

对服务辅助角色进行更改有时可能比较复杂。 使用 Workbox 等库来简化静态资源生成步骤和服务辅助角色代码。

测试 PWA 中的网络连接

了解网络连接何时可用会很有帮助,以便同步数据或通知用户网络状态已更改。

使用以下选项测试网络连接:

属性 navigator.onLine 是一个布尔值,可让你知道网络的当前状态。 如果值为 true,则用户处于联机状态;否则,用户处于脱机状态。

若要了解详细信息,请参阅 MDN 上的 navigator.onLine

联机和脱机事件

可以在网络连接发生更改时采取措施。 可以侦听并采取措施来响应网络事件。 事件在 、 documentdocument.body 元素上window可用,如下所示:

window.addEventListener("online",  function(){
    console.log("You are online!");
});
window.addEventListener("offline", function(){
    console.log("Network connection lost!");
});

若要了解详细信息,请参阅 MDN 上的 Navigator.onLine

其他功能

服务工作者的主要职责是使应用在网络连接不稳定时更快、更可靠。 服务辅助角色通常使用 fetch 事件和 Cache API 来执行此操作,但服务辅助角色可以将其他 API 用于专用方案,例如:

  • 数据的后台同步。
  • 定期同步数据。
  • 大型后台文件下载。
  • 推送消息的处理和通知。

后台同步

使用后台同步 API 允许用户继续使用你的应用并执行操作,即使用户的设备处于脱机状态。

例如,电子邮件应用可以允许其用户随时撰写和发送邮件。 应用前端可以尝试立即发送消息,如果设备脱机,服务辅助角色可以捕获失败的请求,并使用后台同步 API 延迟任务,直到连接。

若要了解详细信息,请参阅 使用后台同步 API 将数据与服务器同步

周期后台同步

定期后台同步 API 允许 PWA 定期在后台检索新内容,以便用户以后再次打开应用时可以立即访问内容。

通过使用定期后台同步 API,PWA 无需在用户使用应用时下载新内容 (如新文章) 。 下载内容可能会减慢体验,因此应用可以在更方便的时间检索内容。

若要了解详细信息,请参阅 使用定期后台同步 API 定期获取新内容

大型后台文件下载

后台提取 API 允许 PWA 将大量数据下载完全委托给浏览器引擎。 这样,应用和服务辅助角色就不必在下载过程中运行。

此 API 适用于允许用户下载大型文件 ((如音乐、电影或播客)) 脱机用例的应用。 下载将委托给浏览器引擎,该引擎知道如何处理间歇性连接,甚至完全丢失连接。

若要了解详细信息,请参阅 在应用或服务辅助角色未运行时使用后台提取 API 提取大型文件

推送消息

推送消息可以发送给用户,而无需他们当时使用应用。 即使应用未运行,服务辅助角色也可以侦听服务器发送的推送消息,并在操作系统的通知中心显示通知。

若要了解详细信息,请参阅 使用推送消息重新吸引用户

使用 DevTools 进行调试

使用 Microsoft Edge DevTools,可以查看服务辅助角色是否已正确注册,以及服务辅助角色当前处于哪个生命周期状态。 此外,还可以在服务辅助角色中调试 JavaScript 代码。

若要了解详细信息,请参阅 调试服务辅助角色

另请参阅