Library Affected:
workbox.backgroundSync.Queue. (Workbox 4.1.1)
Browser & Platform:
Google Chrome v73.0.3683.86 macOS
Issue or Feature Request Description:
When queue.replayRequests() is called and the server is still down, it throws the following error and removes one item from the queue even if it wasn't synced.
sw.js:132 TypeError: Failed to execute 'clone' on 'Request': Request body is already used
at d.g (Queue.mjs:176)
at d.unshiftRequest (Queue.mjs:140)
at d.replayRequests (Queue.mjs:246)
This is the code to register a route for background sync:
const queue = new workbox.backgroundSync.Queue('myQueue')
const addEventRoute = new workbox.routing.Route(
({ url }) => url.pathname === '/api/add',
async args => {
try {
await fetch(args.event.request.clone())
} catch (err) {
console.error(err)
queue.pushRequest(args.event)
}
},
'POST'
)
workbox.routing.registerRoute(addEventRoute)
self.addEventListener('sync', async ev => {
try {
await queue.replayRequests()
} catch (e) {
console.error(e)
}
})
When I try to manually trigger the sync from the UI with:
<Button
variant="contained"
onClick={() => navigator.serviceWorker.ready.then(reg => reg.sync.register('syncEvents'))}
>
sync
</Button>
it fails and removes the oldest entry from the queue.
The sync works well, when the server is reachable again but not when it is still down.
Even if your endpoint is down it workbox is going to replay the request and pop it off the queue, regardless of the return code. I'm currently working around this too, editing the onSync logic based on this post about backgroundSync in workbox v4:
https://github.com/GoogleChrome/workbox/pull/1710
Here is my code. I'm throwing a network error because it seems to be the only one allows me to re-queue the request properly
const collectQueue = new workbox.backgroundSync.Queue('medsinc-collect', {
maxRetentionTime: 60 * 24 * 30,
onSync: async({ queue }) => {
let entry;
while (entry = await queue.shiftRequest()) {
try {
let clone = entry.request.clone()
let response = await fetch(clone)
if (!response.ok) {
throw new Error()
}
}
catch (e) {
await queue.unshiftRequest(entry);
throw {
name: 'NetworkError',
message: 'Server Not Available'
}
return
}
}
}
});
I'm experiencing the same issue in the Chrome on Android (73.0.3683.90):
Uncaught (in promise) TypeError: Failed to execute 'clone' on 'Request': Request body is already used
at Queue._addRequest (Queue.mjs:176)
at Queue.unshiftRequest (Queue.mjs:140)
at Queue.replayRequests (Queue.mjs:246)
The exact same code works in Chrome on Windows (both 73.0.3683.86 and 73.0.3683.103).
My service worker code is as simple as this:
const bgSyncPlugin = new workbox.backgroundSync.Plugin('tag', {
maxRetentionTime: 24 * 60 // Retry for max of 24 Hours
});
workbox.routing.registerRoute(
new RegExp('.+\/a\/b'),
new workbox.strategies.NetworkOnly({
plugins: [bgSyncPlugin]
}),
'POST'
);
Thanks for the report, I've looked into it and it seems there's a place we're not cloning the request before re-fetch()-ing it. And since our tests weren't sending the request to a real server, they weren't reporting the error.
This should be fixed in #2014.
Most helpful comment
Even if your endpoint is down it workbox is going to replay the request and pop it off the queue, regardless of the return code. I'm currently working around this too, editing the onSync logic based on this post about backgroundSync in workbox v4:
https://github.com/GoogleChrome/workbox/pull/1710
Here is my code. I'm throwing a network error because it seems to be the only one allows me to re-queue the request properly