Search code examples
javascriptruntime-errorservice-workerservice-worker-events

Service worker error: event already responded to


I keep getting this error:

Uncaught (in promise) DOMException: Failed to execute 'respondWith' on 'FetchEvent': The event has already been responded to.

I know that service workers automatically respond if asynchronous stuff goes on within the fetch function, but I can't quite work out which bit would be the offender in this code:

importScripts('cache-polyfill.js');

self.addEventListener('fetch', function(event) {

  var location = self.location;

  console.log("loc", location)

  self.clients.matchAll({includeUncontrolled: true}).then(clients => {
    for (const client of clients) {
      const clientUrl = new URL(client.url);
      console.log("SO", clientUrl);
      if(clientUrl.searchParams.get("url") != undefined && clientUrl.searchParams.get("url") != '') {
        location = client.url;
      }
    }

  console.log("loc2", location)

  var url = new URL(location).searchParams.get('url').toString();

  console.log(event.request.hostname);
  var toRequest = event.request.url;
  console.log("Req:", toRequest);

  var parser2 = new URL(location);
  var parser3 = new URL(url);

  var parser = new URL(toRequest);

  console.log("if",parser.host,parser2.host,parser.host === parser2.host);
  if(parser.host === parser2.host) {
    toRequest = toRequest.replace('https://booligoosh.github.io',parser3.protocol + '//' +  parser3.host);
    console.log("ifdone",toRequest);
  }

  console.log("toRequest:",toRequest);

  event.respondWith(httpGet('https://cors-anywhere.herokuapp.com/' + toRequest));
  });
});

function httpGet(theUrl) {
    /*var xmlHttp = new XMLHttpRequest();
    xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
    xmlHttp.send( null );
    return xmlHttp.responseText;*/
    return(fetch(theUrl));
}

Any help would be appreciated.


Solution

  • The problem is that your call to event.respondWith() is inside your top-level promise's .then() clause, meaning that it will be asynchronously executed after the top-level promise resolves. In order to get the behavior you're expecting, event.respondWith() needs to execute synchronously as part of the fetch event handler's execution.

    The logic inside of your promise is a bit hard to follow, so I'm not exactly sure what you're trying to accomplish, but in general you can follow this pattern:

    self.addEventListerner('fetch', event => {
      // Perform any synchronous checks to see whether you want to respond.
      // E.g., check the value of event.request.url.
      if (event.request.url.includes('something')) {
        const promiseChain = doSomethingAsync()
          .then(() => doSomethingAsyncThatReturnsAURL())
          .then(someUrl => fetch(someUrl));
          // Instead of fetch(), you could have called caches.match(),
          // or anything else that returns a promise for a Response.
    
        // Synchronously call event.respondWith(), passing in the
        // async promise chain.
        event.respondWith(promiseChain);
      }
    });
    

    That's the general idea. (The code looks even cleaner if you end up replacing promises with async/await.)