当您向网络服务器发送数据时,请求有时会失败。这可能是因为用户连接中断,或者服务器已关闭;无论是哪种情况,您通常需要稍后再次尝试发送请求。
新的 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
。这意味着,如果收到具有 4xx
或 5xx
错误状态的响应,则不会重试请求。如果您想重试导致出现 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 有点不直观且困难。
测试您的实现的最佳方法是执行以下操作:
- 加载页面并注册 Service Worker。
- 关闭计算机的网络或关闭网络服务器。
- 请勿离线使用 Chrome 开发者工具。开发者工具中的离线复选框只会影响来自该页面的请求。Service Worker 请求将继续处理。
- 发出应该使用 Workbox 后台同步排入队列的网络请求。
- 您可以在
Chrome DevTools > Application > IndexedDB > workbox-background-sync > requests
中查看请求,以查看请求是否已排入队列。
- 您可以在
- 现在,打开网络或网络服务器。
强制提前
sync
事件,具体方法是:前往Chrome DevTools > Application > Service Workers
,输入workbox-background-sync:<your queue name>
的标记名称,其中<your queue name>
应为您设置的队列名称,然后点击“同步”按钮。您应该会看到失败的请求的网络请求以及 IndexedDB 数据现在应该为空,因为请求已成功重放。
类型
BackgroundSyncPlugin
实现 fetchDidFail
生命周期回调的类。这样可以更轻松地将失败的请求添加到后台同步队列。
属性
-
构造函数
void
constructor
函数如下所示:(name: string, options?: QueueOptions) => {...}
-
name
string
如需了解参数详情,请参阅
workbox-background-sync.Queue
文档。 -
选项
QueueOptions 可选
-
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
请求
-
返回
Promise<StorableRequest>
-