Synchronous or Sequential fetch in Service Worker

2019-06-24 05:21发布

问题:

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.

回答1:

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);
  });
}


回答2:

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(). (NetworkErrors 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.
})