Может ли работник службы извлекать и кэшировать активы из разных источников? - PullRequest
0 голосов
/ 10 февраля 2019

Я использую некоторый код работника сервиса из учебника Progressive Web от Google , но получаю сообщение об ошибке:

Uncaught (in promise) TypeError:
 Failed to execute 'clone' on 'Response':
 Response body is already used

Сайт использует сторонние Javascript и таблицы стилейдля веб-шрифтов.Я хочу добавить ресурсы, размещенные на этих CDN, в автономный кеш.

addEventListener("fetch", function(e) {
  e.respondWith(
    caches.match(e.request).then(function(response) {
        return response || fetch(e.request).then(function(response) {
        var hosts = [
          "https://fonts.googleapis.com",
          "https://maxcdn.bootstrapcdn.com",
          "https://cdnjs.cloudflare.com"
        ];
        hosts.map(function(host) {
          if (e.request.url.indexOf(host) === 0) {
            caches.open(CACHE_NAME).then(function(cache) {
              cache.put(e.request, response.clone());
            });
          }
        });
        return response;
      });
    })
  );
});

Они размещены на популярных CDN, так что я догадываюсь, что они должны делать правильные вещи для заголовков CORS.

Вот ресурсы в HTML, которые я хочу кэшировать:

<link rel="stylesheet" type="text/css"
      href="https://fonts.googleapis.com/css?family=Merriweather:900,900italic,300,300italic">
<link rel="stylesheet" type="text/css"
      href="https://fonts.googleapis.com/css?family=Lato:900,300" rel="stylesheet">
<link rel="stylesheet" type="text/css"
      href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
<script type="text/javascript" async
        src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>

Согласно журналам консоли, работник службы пытается извлечь эти активы:

Fetch finished loading:
 GET "https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css".
 sw.js:32
Fetch finished loading:
 GET "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML".
 sw.js:32
Fetch finished loading:
 GET "https://fonts.googleapis.com/css?family=Merriweather:900,900italic,300,300italic".
 sw.js:32
Fetch finished loading:
 GET "https://fonts.googleapis.com/css?family=Lato:900,300".
 sw.js:32
Fetch finished loading:
 GET "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/config/TeX-AMS-MML_HTMLorMML.js?V=2.7.1".
 sw.js:32
Fetch finished loading:
 GET "https://maxcdn.bootstrapcdn.com/font-awesome/latest/fonts/fontawesome-webfont.woff2?v=4.7.0".
 sw.js:32

Если я удалю clone, как было предложено в Почему запрос на выборку должен быть клонирован в сервисном работнике? , я получу ту же ошибку:

TypeError: Response body is already used

Если я добавлю { mode: "no-cors" } к выборке для Служебная проблема CORS , я получу ту же ошибку и эти предупреждения:

The FetchEvent for
 "https://maxcdn.bootstrapcdn.com/font-awesome/latest/fonts/fontawesome-webfont.woff2?v=4.7.0"
 resulted in a network error response: an "opaque" response was
 used for a request whose type is not no-cors
The FetchEvent for
 "https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh50XSwiPGQ3q5d0.woff2"
 resulted in a network error response: an "opaque" response was
 used for a request whose type is not no-cors
The FetchEvent for
 "https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2"
 resulted in a network error response: an "opaque" response was
 used for a request whose type is not no-cors
The FetchEvent for
 "https://fonts.gstatic.com/s/merriweather/v19/u-4n0qyriQwlOrhSvowK_l521wRZWMf6hPvhPQ.woff2"
 resulted in a network error response: an "opaque" response was
 used for a request whose type is not no-cors

Я мог бы добавить эти активы кстатический кеш в событии install работника сервиса, но у меня есть причины добавлять их в кеш только в событии fetch.

1 Ответ

0 голосов
/ 13 февраля 2019

Вы на правильном пути, используя clone(), но время важно.Вы должны убедиться, что вы вызываете clone() до того, как будет выполнен финальный return response, потому что в этот момент ответ будет передан на страницу клиента работника сервиса, и его тело будет "использовано".

Есть два способа исправить это: либо вызвать clone() перед выполнением асинхронного кода кэширования, либо, альтернативно, отложить ваш оператор return response до завершения кэширования.

Я собираюсь предложитьпервый подход, так как это означает, что вы получите ответ на страницу как можно скорее.Я также собираюсь предложить вам переписать свой код для использования async / await, так как он гораздо более читабелен (и поддерживается любым браузером, который также поддерживает сервисных работников сегодня).

addEventListener("fetch", function(e) {
  e.respondWith((async function() {
    const cachedResponse = await caches.match(e.request);
    if (cachedResponse) {
      return cachedResponse;
    }

    const networkResponse = await fetch(e.request);

    const hosts = [
      'https://fonts.googleapis.com',
      'https://maxcdn.bootstrapcdn.com',
      'https://cdnjs.cloudflare.com',
    ];

    if (hosts.some((host) => e.request.url.startsWith(host))) {
      // This clone() happens before `return networkResponse` 
      const clonedResponse = networkResponse.clone();

      e.waitUntil((async function() {
        const cache = await caches.open(CACHE_NAME);
        // This will be called after `return networkResponse`
        // so make sure you already have the clone!
        await cache.put(e.request, clonedResponse);
      })());
    }

    return networkResponse;
  })());
});

Примечание. Синтаксис (async function() {})() может показаться немного странным, но это ярлык для использования async / await внутри сразу выполняющейся функции, которая будет возвращать обещание.См. http://2ality.com/2016/10/async-function-tips.html#immediately-invoked-async-function-expressions

. Для получения исходного кода необходимо клонировать ответ перед выполнением асинхронного обновления кэша:

        var clonedResponse = response.clone();
        caches.open(CACHE_NAME).then(function(cache) {
          cache.put(e.request, clonedResponse);
        });

Учебник по Service Worker от Google имеет пример кода, показывающий правильный путь.В коде есть комментарий с «важной» заметкой, но он просто подчеркивает клон, а не проблему, с которой вы сталкиваетесь при клонировании:

        // IMPORTANT: Clone the response. A response is a stream
        // and because we want the browser to consume the response
        // as well as the cache consuming the response, we need
        // to clone it so we have two streams.
        var responseToCache = response.clone();

        caches.open(CACHE_NAME)
          .then(function(cache) {
            cache.put(event.request, responseToCache);
          });

        return response;
...