Ошибка сервисного работника: на событие уже ответили

Я продолжаю получать эту ошибку:

Uncaught (в обещании) DOMException: не удалось выполнить «respondWith» для «FetchEvent»: на событие уже был дан ответ.

Я знаю, что сервис-воркеры автоматически реагируют, если в функции fetch происходят асинхронные вещи, но я не могу понять, какой бит будет нарушителем в этом коде:

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

Любая помощь будет оценена по достоинству.


person Ethan    schedule 19.10.2017    source источник


Ответы (2)


Проблема в том, что ваш вызов event.respondWith() находится внутри предложения .then() вашего промиса верхнего уровня, а это означает, что он будет выполняться асинхронно после разрешения промиса верхнего уровня. Чтобы получить ожидаемое поведение, event.respondWith() должен выполняться синхронно как часть выполнения обработчика событий fetch.

Логика внутри вашего обещания немного сложна для понимания, поэтому я не совсем уверен, чего вы пытаетесь достичь, но в целом вы можете следовать этому шаблону:

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

Это общая идея. (Код выглядит еще чище, если вы замените promises на async/await.)

person Jeff Posnick    schedule 19.10.2017

Я также наткнулся на эту ошибку, пытаясь использовать async/await в обработчике выборки. Как упомянул Джефф в своем ответе, event.respondWith должен вызываться синхронно, а параметр может быть любым, что возвращает обещание, которое разрешается в ответ. Поскольку асинхронные функции действительно возвращают промис, все, что вам нужно сделать, это обернуть логику выборки внутри асинхронной функции, которая в какой-то момент возвращает объект ответа и вызвать event.respondWith с этим обработчиком.

async function handleRequest(request) {
  const response = await fetch(request)

  // ...perform additional logic

  return response
}

self.addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request));
});
person Pier-Luc Gendreau    schedule 30.09.2018