Workbox-background-sync

当您向网络服务器发送数据时,请求有时会失败。这可能是因为用户连接中断,或者服务器已关闭;无论是哪种情况,您通常需要稍后再次尝试发送请求。

新的 BackgroundSync API 是解决此问题的理想解决方案。当 Service Worker 检测到网络请求失败时,可以注册接收 sync 事件,当浏览器认为连接已返回时,就会传递该事件。请注意,即使用户已经离开应用,系统仍可传递同步事件,这比重试失败请求的传统方法有效得多。

Workbox 后台同步旨在让您更轻松地使用 BackgroundSync API,并将其用法与其他 Workbox 模块集成。它还针对尚未实现 BackgroundSync 的浏览器实现了回退策略。

支持 BackgroundSync API 的浏览器会根据由浏览器管理的时间间隔代表您自动重放失败的请求,并且可能会在两次重放尝试之间使用指数退避算法。在原生支持 BackgroundSync API 的浏览器中,每当 Service Worker 启动时,Workbox 后台同步都会自动尝试重新执行。

基本用法

使用后台同步的最简单方法是使用 Plugin,它会自动将失败的请求加入队列,并在将来的 sync 事件触发时重试。

import {BackgroundSyncPlugin} from 'workbox-background-sync';
import {registerRoute} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';

const bgSyncPlugin = new BackgroundSyncPlugin('myQueueName', {
  maxRetentionTime: 24 * 60, // Retry for max of 24 Hours (specified in minutes)
});

registerRoute(
  /\/api\/.*\/*.json/,
  new NetworkOnly({
    plugins: [bgSyncPlugin],
  }),
  'POST'
);

BackgroundSyncPlugin 连接到 fetchDidFail 插件回调,并且仅在出现异常(很可能是网络故障)时调用 fetchDidFail。这意味着,如果收到具有 4xx5xx 错误状态的响应,则不会重试请求。如果您想重试导致出现 5xx 状态等问题的所有请求,可以通过向策略添加 fetchDidSucceed 插件来实现:

const statusPlugin = {
  fetchDidSucceed: ({response}) => {
    if (response.status >= 500) {
      // Throwing anything here will trigger fetchDidFail.
      throw new Error('Server error.');
    }
    // If it's not 5xx, use the response as-is.
    return response;
  },
};

// Add statusPlugin to the plugins array in your strategy.

高级用法

Workbox 后台同步还提供了一个 Queue 类,可将其实例化并添加失败的请求。失败的请求存储在 IndexedDB 中,并在浏览器认为连接已恢复(即收到同步事件时)时重试。

创建队列

如需创建 Workbox 后台同步队列,您需要使用一个队列名称(该名称在您的 origin 中必须是唯一的)来构建它:

import {Queue} from 'workbox-background-sync';

const queue = new Queue('myQueueName');

队列名称用作由全局 SyncManager 进行 register() 处理的标记名称的一部分。此名称还可用作 IndexedDB 数据库的对象存储名称。

向队列添加请求

创建队列实例后,可以向其添加失败的请求。您可以通过调用 .pushRequest() 方法来添加失败的请求。例如,以下代码会捕获任何失败的请求,并将其添加到队列中:

import {Queue} from 'workbox-background-sync';

const queue = new Queue('myQueueName');

self.addEventListener('fetch', event => {
  // Add in your own criteria here to return early if this
  // isn't a request that should use background sync.
  if (event.request.method !== 'POST') {
    return;
  }

  const bgSyncLogic = async () => {
    try {
      const response = await fetch(event.request.clone());
      return response;
    } catch (error) {
      await queue.pushRequest({request: event.request});
      return error;
    }
  };

  event.respondWith(bgSyncLogic());
});

请求被添加到队列后,当 Service Worker 收到 sync 事件(浏览器认为连接恢复时就会发生这种情况)会自动重试。不支持 BackgroundSync API 的浏览器会在每次 Service Worker 启动时重试队列。这要求控制 Service Worker 的页面正在运行,因此效果不太理想。

测试 Workbox 后台同步

遗憾的是,由于多种原因,测试 BackgroundSync 有点不直观且困难。

测试您的实现的最佳方法是执行以下操作:

  1. 加载页面并注册 Service Worker。
  2. 关闭计算机的网络或关闭网络服务器。
    • 请勿离线使用 Chrome 开发者工具。开发者工具中的离线复选框只会影响来自该页面的请求。Service Worker 请求将继续处理。
  3. 发出应该使用 Workbox 后台同步排入队列的网络请求。
    • 您可以在 Chrome DevTools > Application > IndexedDB > workbox-background-sync > requests 中查看请求,以查看请求是否已排入队列。
  4. 现在,打开网络或网络服务器。
  5. 强制提前 sync 事件,具体方法是:前往 Chrome DevTools > Application > Service Workers,输入 workbox-background-sync:<your queue name> 的标记名称,其中 <your queue name> 应为您设置的队列名称,然后点击“同步”按钮。

    Chrome 开发者工具中的“同步”按钮示例

  6. 您应该会看到失败的请求的网络请求以及 IndexedDB 数据现在应该为空,因为请求已成功重放。

类型

BackgroundSyncPlugin

实现 fetchDidFail 生命周期回调的类。这样可以更轻松地将失败的请求添加到后台同步队列。

属性

Queue

用于管理在 IndexedDB 中存储失败请求并在稍后重试这些请求的类。存储和重放过程的所有部分都可以通过回调来观察。

属性

  • 构造函数

    void

    使用指定选项创建队列实例

    constructor 函数如下所示:

    (name: string,options?: QueueOptions)=> {...}

    • name

      string

      此队列的唯一名称。此名称必须是唯一的,因为它用于注册同步事件,以及将特定于此实例的 IndexedDB 存储在 IndexedDB 中。如果检测到重复名称,则会抛出错误。

    • 选项

      QueueOptions 可选

  • name

    string

  • getAll

    void

    返回所有未过期的条目(每个 maxRetentionTime)。所有过期的条目都将从队列中移除。

    getAll 函数如下所示:

    ()=> {...}

    • 返回

      Promise<QueueEntry[]>

  • popRequest

    void

    移除并返回队列中的最后一个请求(及其时间戳和所有元数据)。返回的对象采用以下形式:{request, timestamp, metadata}

    popRequest 函数如下所示:

    ()=> {...}

    • 返回

      Promise<QueueEntry>

  • pushRequest

    void

    将传递的请求存储在队列末尾的 IndexedDB(及其时间戳和任何元数据)中。

    pushRequest 函数如下所示:

    (entry: QueueEntry)=> {...}

    • 入口

      QueueEntry

    • 返回

      Promise<void>

  • registerSync

    void

    使用在此实例中独有的代码注册同步事件。

    registerSync 函数如下所示:

    ()=> {...}

    • 返回

      Promise<void>

  • replayRequests

    void

    循环遍历队列中的各个请求,并尝试重新获取该请求。 如果任何请求未能重新提取,系统会将其放回到队列中的同一位置(这会为下一个同步事件注册重试)。

    replayRequests 函数如下所示:

    ()=> {...}

    • 返回

      Promise<void>

  • shiftRequest

    void

    移除并返回队列中的第一个请求(及其时间戳和所有元数据)。返回的对象采用以下形式:{request, timestamp, metadata}

    shiftRequest 函数如下所示:

    ()=> {...}

    • 返回

      Promise<QueueEntry>

  • 大小

    void

    返回队列中存在的条目数。 请注意,过期的条目数(每个 maxRetentionTime)也包含在此计数中。

    size 函数如下所示:

    ()=> {...}

    • 返回

      Promise<数字>

  • unshiftRequest

    void

    将传递的请求存储在 IndexedDB(及其时间戳和任何元数据)中的队列开头的位置。

    unshiftRequest 函数如下所示:

    (entry: QueueEntry)=> {...}

    • 入口

      QueueEntry

    • 返回

      Promise<void>

QueueOptions

属性

  • forceSyncFallback

    布尔值 选填

  • maxRetentionTime

    数字可选

  • onSync

    OnSyncCallback 可选

QueueStore

一个类,用于管理 IndexedDB 中的队列请求存储,并按队列名称编入索引,以便于访问。

大多数开发者不需要直接访问此类,而是面向高级用例公开。

属性

  • 构造函数

    void

    将此实例与队列实例相关联,以便添加的条目可以通过其队列名称来识别。

    constructor 函数如下所示:

    (queueName: string)=> {...}

    • queueName

      string

  • deleteEntry

    void

    删除指定 ID 的条目。

    警告:此方法不能确保已删除的条目属于此队列(即与 queueName 匹配)。但此限制是可接受的,因为此类未公开提供。一项额外的检查会导致此方法的运行速度变慢。

    deleteEntry 函数如下所示:

    (id: number)=> {...}

    • id

      number

    • 返回

      Promise<void>

  • getAll

    void

    返回存储区中与 queueName 匹配的所有条目。

    getAll 函数如下所示:

    ()=> {...}

    • 返回

      Promise<QueueStoreEntry[]>

  • popEntry

    void

    移除并返回队列中与 queueName 匹配的最后一个条目。

    popEntry 函数如下所示:

    ()=> {...}

    • 返回

      Promise<QueueStoreEntry>

  • pushEntry

    void

    将条目附加到队列中。

    pushEntry 函数如下所示:

    (entry: UnidentifiedQueueStoreEntry)=> {...}

    • 入口

      UnidentifiedQueueStoreEntry

    • 返回

      Promise<void>

  • shiftEntry

    void

    移除并返回队列中与 queueName 匹配的第一个条目。

    shiftEntry 函数如下所示:

    ()=> {...}

    • 返回

      Promise<QueueStoreEntry>

  • 大小

    void

    返回存储区中与 queueName 匹配的条目数。

    size 函数如下所示:

    ()=> {...}

    • 返回

      Promise<数字>

  • unshiftEntry

    void

    先在队列中前置一个条目。

    unshiftEntry 函数如下所示:

    (entry: UnidentifiedQueueStoreEntry)=> {...}

    • 入口

      UnidentifiedQueueStoreEntry

    • 返回

      Promise<void>

StorableRequest

一个类,可让您更轻松地对请求进行序列化和反序列化,以便将其存储在 IndexedDB 中。

大多数开发者不需要直接访问此类,而是面向高级用例公开。

属性

  • 构造函数

    void

    接受请求数据的对象,该对象可用于构造 Request,但也可以存储在 IndexedDB 中。

    constructor 函数如下所示:

    (requestData: RequestData)=> {...}

    • requestData

      RequestData

      请求数据的对象,包含 url 以及 [requestInit]https://fetch.spec.whatwg.org/#requestinit 的任何相关属性。

  • clone

    void

    创建并返回实例的深度克隆。

    clone 函数如下所示:

    ()=> {...}

  • toObject

    void

    返回实例 _requestData 对象的深度克隆。

    toObject 函数如下所示:

    ()=> {...}

    • 返回

      RequestData

  • toRequest

    void

    将此实例转换为请求。

    toRequest 函数如下所示:

    ()=> {...}

    • 返回

      请求

  • fromRequest

    void

    将 Request 对象转换为可结构化克隆或 JSON 字符串化的普通对象。

    fromRequest 函数如下所示:

    (request: Request)=> {...}

    • request

      请求