When you send data to a web server, sometimes the requests will fail. It may be because the user has lost connectivity, or it may be because the server is down; in either case you often want to try sending the requests again later.
The new BackgroundSync API
is an ideal solution to this problem. When a service worker detects that a
network request has failed, it can register to receive a sync
event,
which gets delivered when the browser thinks connectivity has returned.
Note that the sync event can be delivered even if the user has left the
application, making it much more effective than the traditional method of
retrying failed requests.
Workbox Background Sync is designed to make it easier to use the BackgroundSync API and integrate its usage with other Workbox modules. It also implements a fallback strategy for browsers that don't yet implement BackgroundSync.
Browsers that support the BackgroundSync API will automatically replay failed requests on your behalf at an interval managed by the browser, likely using exponential backoff between replay attempts. In browsers that don't natively support the BackgroundSync API, Workbox Background Sync will automatically attempt a replay whenever your service worker starts up.
Basic Usage
The easiest way to use Background Sync is to use the Plugin
that will
automatically Queue up failed requests and retry them when future sync
events are fired.
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
hooks into the
fetchDidFail
plugin callback, and
fetchDidFail
is only invoked if there's an exception thrown, most likely due
to a network failure. This means that requests won't be retried if there's a
response received with a
4xx
or 5xx
error status.
If you would like to retry all requests that result in, e.g., a 5xx
status,
you can do so by
adding a fetchDidSucceed
plugin
to your strategy:
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.
Advanced Usage
Workbox Background Sync also provides a Queue
class, which you can
instantiate and add failed requests to. The failed requests are stored
in IndexedDB
and are retried when the browser thinks connectivity is restored (i.e.
when it receives the sync event).
Creating a Queue
To create a Workbox Background Sync Queue you need to construct it with a queue name (which must be unique to your origin):
import {Queue} from 'workbox-background-sync';
const queue = new Queue('myQueueName');
The queue name is used as part of the tag name that gets
register()
-ed
by the global
SyncManager
. It's
also used as the
Object Store name for
the IndexedDB database.
Adding a request to the Queue
Once you've created your Queue instance, you can add failed requests to it.
You add failed request by invoking the .pushRequest()
method. For example,
the following code catches any requests that fail and adds them to the queue:
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());
});
Once added to the queue, the request is automatically retried when the
service worker receives the sync
event (which happens when the browser
thinks connectivity is restored). Browsers that don't support the
BackgroundSync API will retry the queue every time the service worker is
started up. This requires the page controlling the service worker to be
running, so it won't be quite as effective.
Testing Workbox Background Sync
Sadly, testing BackgroundSync is somewhat unintuitive and difficult for a number of reasons.
The best approach to test your implementation is to do the following:
- Load up a page and register your service worker.
- Turn off your computer's network or turn off your web server.
- DO NOT USE CHROME DEVTOOLS OFFLINE. The offline checkbox in DevTools only affects requests from the page. Service Worker requests will continue to go through.
- Make network requests that should be queued with Workbox Background Sync.
- You can check the requests have been queued by looking in
Chrome DevTools > Application > IndexedDB > workbox-background-sync > requests
- You can check the requests have been queued by looking in
- Now turn on your network or web server.
Force an early
sync
event by going toChrome DevTools > Application > Service Workers
, entering the tag name ofworkbox-background-sync:<your queue name>
where<your queue name>
should be the name of the queue you set, and then clicking the 'Sync' button.You should see network requests go through for the failed requests and the IndexedDB data should now be empty since the requests have been successfully replayed.
Types
BackgroundSyncPlugin
A class implementing the fetchDidFail
lifecycle callback. This makes it
easier to add failed requests to a background sync Queue.
Properties
-
constructor
void
The
constructor
function looks like:(name: string, options?: QueueOptions) => {...}
-
name
string
See the
workbox-background-sync.Queue
documentation for parameter details. -
options
QueueOptions optional
-
returns
-
Queue
A class to manage storing failed requests in IndexedDB and retrying them later. All parts of the storing and replaying process are observable via callbacks.
Properties
-
constructor
void
Creates an instance of Queue with the given options
The
constructor
function looks like:(name: string, options?: QueueOptions) => {...}
-
name
string
The unique name for this queue. This name must be unique as it's used to register sync events and store requests in IndexedDB specific to this instance. An error will be thrown if a duplicate name is detected.
-
options
QueueOptions optional
-
returns
-
-
name
string
-
getAll
void
Returns all the entries that have not expired (per
maxRetentionTime
). Any expired entries are removed from the queue.The
getAll
function looks like:() => {...}
-
returns
Promise<QueueEntry[]>
-
-
popRequest
void
Removes and returns the last request in the queue (along with its timestamp and any metadata). The returned object takes the form:
{request, timestamp, metadata}
.The
popRequest
function looks like:() => {...}
-
returns
Promise<QueueEntry>
-
-
pushRequest
void
Stores the passed request in IndexedDB (with its timestamp and any metadata) at the end of the queue.
The
pushRequest
function looks like:(entry: QueueEntry) => {...}
-
entry
QueueEntry
-
returns
Promise<void>
-
-
registerSync
void
Registers a sync event with a tag unique to this instance.
The
registerSync
function looks like:() => {...}
-
returns
Promise<void>
-
-
replayRequests
void
Loops through each request in the queue and attempts to re-fetch it. If any request fails to re-fetch, it's put back in the same position in the queue (which registers a retry for the next sync event).
The
replayRequests
function looks like:() => {...}
-
returns
Promise<void>
-
-
shiftRequest
void
Removes and returns the first request in the queue (along with its timestamp and any metadata). The returned object takes the form:
{request, timestamp, metadata}
.The
shiftRequest
function looks like:() => {...}
-
returns
Promise<QueueEntry>
-
-
size
void
Returns the number of entries present in the queue. Note that expired entries (per
maxRetentionTime
) are also included in this count.The
size
function looks like:() => {...}
-
returns
Promise<number>
-
-
unshiftRequest
void
Stores the passed request in IndexedDB (with its timestamp and any metadata) at the beginning of the queue.
The
unshiftRequest
function looks like:(entry: QueueEntry) => {...}
-
entry
QueueEntry
-
returns
Promise<void>
-
QueueOptions
Properties
-
forceSyncFallback
boolean optional
-
maxRetentionTime
number optional
-
onSync
OnSyncCallback optional
QueueStore
A class to manage storing requests from a Queue in IndexedDB, indexed by their queue name for easier access.
Most developers will not need to access this class directly; it is exposed for advanced use cases.
Properties
-
constructor
void
Associates this instance with a Queue instance, so entries added can be identified by their queue name.
The
constructor
function looks like:(queueName: string) => {...}
-
queueName
string
-
returns
-
-
deleteEntry
void
Deletes the entry for the given ID.
WARNING: this method does not ensure the deleted entry belongs to this queue (i.e. matches the
queueName
). But this limitation is acceptable as this class is not publicly exposed. An additional check would make this method slower than it needs to be.The
deleteEntry
function looks like:(id: number) => {...}
-
id
number
-
returns
Promise<void>
-
-
getAll
void
Returns all entries in the store matching the
queueName
.The
getAll
function looks like:() => {...}
-
returns
Promise<QueueStoreEntry[]>
-
-
popEntry
void
Removes and returns the last entry in the queue matching the
queueName
.The
popEntry
function looks like:() => {...}
-
returns
Promise<QueueStoreEntry>
-
-
pushEntry
void
Append an entry last in the queue.
The
pushEntry
function looks like:(entry: UnidentifiedQueueStoreEntry) => {...}
-
entry
UnidentifiedQueueStoreEntry
-
returns
Promise<void>
-
-
shiftEntry
void
Removes and returns the first entry in the queue matching the
queueName
.The
shiftEntry
function looks like:() => {...}
-
returns
Promise<QueueStoreEntry>
-
-
size
void
Returns the number of entries in the store matching the
queueName
.The
size
function looks like:() => {...}
-
returns
Promise<number>
-
-
unshiftEntry
void
Prepend an entry first in the queue.
The
unshiftEntry
function looks like:(entry: UnidentifiedQueueStoreEntry) => {...}
-
entry
UnidentifiedQueueStoreEntry
-
returns
Promise<void>
-
StorableRequest
A class to make it easier to serialize and de-serialize requests so they can be stored in IndexedDB.
Most developers will not need to access this class directly; it is exposed for advanced use cases.
Properties
-
constructor
void
Accepts an object of request data that can be used to construct a
Request
but can also be stored in IndexedDB.The
constructor
function looks like:(requestData: RequestData) => {...}
-
requestData
RequestData
An object of request data that includes the
url
plus any relevant properties of [requestInit]https://fetch.spec.whatwg.org/#requestinit
.
-
returns
-
-
clone
void
Creates and returns a deep clone of the instance.
The
clone
function looks like:() => {...}
-
returns
-
-
toObject
void
Returns a deep clone of the instances
_requestData
object.The
toObject
function looks like:() => {...}
-
returns
RequestData
-
-
toRequest
void
Converts this instance to a Request.
The
toRequest
function looks like:() => {...}
-
returns
Request
-
-
fromRequest
void
Converts a Request object to a plain object that can be structured cloned or JSON-stringified.
The
fromRequest
function looks like:(request: Request) => {...}
-
request
Request
-
returns
Promise<StorableRequest>
-