This question already has an answer here:
-
Resolve promises one after another (i.e. in sequence)?
23 answers
I need to send a series of PUT & POST requests from a Service Worker. The order in which they're sent matters.
Requirements:
- Given a request method, url, and JSON body, send the request
- If it succeeds (
response.status < 300
):
- Pass body to a success function
- Call next request in queue
- If it fails:
- Pass responseText or err to error function
- Stop execution
If I simply iterate through the queue and call fetch
for each request, network variance can (often does) cause the requests to arrive at the server out of order.
How do I make a chain of fetch
requests, where each result depends on the success of the previous one?
What I've tried:
- XHR instead (assuming I could use "async: false", but this is not allowed in Service Worker).
setTimeout(sendRequest, i*200)
. A hack, not reliable.
Promise loops
based off of these examples ES6 Promise Patterns. This seemed most promising, but the examples are for a simple case where success is assumed. Can't get it to work with fetch.
Context: I'm using an "outbox" of API requests to support offline reading, creating, and updating of data. Works well, except for this ordering issue.
Instead of turning each request in the queue into a Promise right away, why not just pop entries off the queue as you need them?
var workQueue = [work, goes, here];
var currentItem = workQueue.shift();
return performWorkWith(currentItem)
.then(handleResponseWithQueue(workQueue));
function handleResponseWithQueue(queue) {
return function handleResponse(response) {
if (response.ok && queue.length > 0)
return performWorkWith(queue.shift()).then(handleResponseWithQueue(queue));
};
}
You can generalize this pattern to (simplified):
function series(work, queue) {
if (queue.length <= 0) return;
work(queue.shift()).then(function() {
if (queue.length > 0) return series(work, queue);
});
}
I think you're going to want to follow the Sync loop pattern from that ES6 Promise Patterns page.
Once your "success" promise chain is set up via .reduce()
, you can attach a single .catch()
clause to the end to handle the error reporting. Any promise rejection/throw
inside of the promise chain will short-circuit all of the .then()
s and jump straight to your .catch()
at the end.
In order for this to work as you describe, you'll want to explicitly check for an error HTTP response status in your fetch(...).then(...)
and throw
if you encounter one, since a HTTP error response won't otherwise trigger a .catch()
. (NetworkError
s or similar runtime exceptions will trigger the .catch()
, though.) Something like:
fetch('https://example.com').then(response => {
if (!response.ok) { // See https://fetch.spec.whatwg.org/#ok-status
throw new Error('Invalid HTTP response: ' + response.status);
}
// Otherwise, do something with the valid response.
})