服务工作者可以获取和缓存跨域资产吗? [英] Can a service worker fetch and cache cross-origin assets?

查看:78
本文介绍了服务工作者可以获取和缓存跨域资产吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用渐进式网络中的一些服务人员代码Google的应用教程,但出现错误:

I'm using some service worker code from the Progressive Web app tutorial by Google but I am getting an error:


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

该网站将第三方Javascript和样式表用于网络字体。
我想将这些CDN上托管的资产添加到脱机缓存中。

The site uses third-party Javascript and stylesheets for web fonts. I want to add assets hosted on these CDNs to the offline cache.

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标头做正确的事情。

These are hosted on popular CDNs, so my hunch is they should be doing the right thing for CORS headers.

以下是我要缓存的HTML资产:

Here are the assets in the HTML that I want to cache:

<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>

根据控制台日志,服务工作者正在尝试来获取这些日志资产:

According to the console logs, the service worker is trying to fetch these assets:


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

如果我删除了克隆,如为什么必须在服务工作者中克隆获取请求?所示,我将得到相同的错误:

If I remove the clone, as was suggested in Why does fetch request have to be cloned in service worker?, I'll get the same error:


TypeError: Response body is already used

如果我将 {模式: no-cors} 添加到每次服务工人CORS问题,我将得到相同的错误和以下警告:

If I add { mode: "no-cors" } to the fetch per Service worker CORS issue, I'll get the same error and these warnings:


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 事件中将这些资产添加到静态缓存中,但是我有理由仅在<$中将它们添加到缓存中c $ c>获取事件。

I could add these assets to the static cache in the service worker's install event, but I have reasons to add them to the cache only in the fetch event.

推荐答案

使用 clone() ,但是时间很重要。您需要确保在执行最后的返回响应之前调用 clone(),因为在这一点上,响应将被传递到服务人员的客户端页面,其主体将被消耗。

You're on the right track with using clone(), but the timing is important. You need to make sure that you call clone() before the final return response executes, because at that point, the response will be passed to the service worker's client page, and its body will be "consumed".

有两种解决方法:要么调用 clone(),然后执行异步缓存代码,或者将返回响应语句延迟到缓存完成之后。

There are two ways of fixing this: either call clone() prior to executing the asynchronous caching code, or alternatively, delay your return response statement until after the caching has completed.

我将建议第一种方法,因为这意味着您最终将尽快获得对页面的响应。我还建议您重写代码以使用 async / await ,因为它更具可读性(并且也受任何支持的浏览器支持

I'm going to suggest the first approach, since it means you'll end up getting the response to the page as soon as possible. I'm also going to suggest that you rewrite your code to use async/await, as it's much more readable (and supported by any browser that also supports service workers today).

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 / await 的快捷方式执行将返回承诺的函数。参见 http://2ality.com /2016/10/async-function-tips.html#immediately-invoked-async-function-expressions

Note: The (async function() {})() syntax might look a little weird, but it's a shortcut to use async/await inside an immediately executing function that will return a promise. See http://2ality.com/2016/10/async-function-tips.html#immediately-invoked-async-function-expressions

对于原始代码,您需要克隆在执行异步缓存更新之前的响应:

For the original code, you need to clone the response before you do the asynchronous cache update:

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

Google的Service Worker入门中的示例代码显示了正确的方法。该代码带有带重要注解的注释,但这只是强调克隆,而不是克隆时遇到的问题:

The Service Worker primer by Google has example code showing the correct way. The code has a comment with an "important" note, but it's just emphasizing the clone, and not the issue you're having about when you clone:

        // 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;

这篇关于服务工作者可以获取和缓存跨域资产吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆