-->

Why does fetch request have to be cloned in servic

2020-08-11 00:04发布

问题:

In one of the Service Worker examples by Google, cache and return requests

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // IMPORTANT: Clone the request. A request is a stream and
        // can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need
        // to clone the response.
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(
          function(response) {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

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

On the other hand, the example provided by MDN, Using Service Workers, does not clone the request.

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(resp) {
      return resp || fetch(event.request).then(function(response) {
        caches.open('v1').then(function(cache) {
          cache.put(event.request, response.clone());
        });
        return response;
      });
    }).catch(function() {
      return caches.match('/sw-test/gallery/myLittleVader.jpg');
    })
  );
});

So in the case of a cache miss in the Google example:

I understand why we have to clone the response: because it's consumed by cache.put, and we still want to return the response back to the webpage who requested it.

But why does one have to clone the request? In the comment it says it's consumed by cache and the browser for fetch. What does it mean exactly?

  • Where in the cache is the request stream consumed? cache.put? If so, why doesn't caches.match consume the request?

回答1:

The comment seems to me to say quite clearly why the author of that code thought cloning was necessary:

A request is a stream and can only be consumed once. Since we are consuming this once by cache and once by the browser for fetch, we need to clone the response.

Remember that the body of a request can be a ReadableStream. If cache.match had to read the stream (or partially read the stream) to know whether a cache entry was a match, a subsequent read by fetch would continue that read, missing any data that cache.match read.

I wouldn't be surprised if it only mattered in limited situations (unless the code in the Google example is just plain wrong and it's not necessary), and so failing to do it probably works in many test cases (for instance, where the body is null or a string, not a stream). Remember that MDN is very good, but it is community-edited, and errors and poor examples do creep in periodically. (I've had to fix several blatant errors in it over the years.) Usually the community spots them and fixes them.



回答2:

fetch requests, aren't cached and hence caches do not essentially consume a request

Hence, no need to clone. - asked Jake on his write up earlier.

responses however, are put or added into a cache and /or, can be passed down the then chain as JSON /text/ something else - meaning, they are / can be consumed.

My guess is, If you use or mutate something, then, you need to clone it.

A read is possibly doing neither in caches.match

I am also assuming , that possibly another reason is, the read on the request itself, is not piped down the chain, and is only read once by caches.match as in, the read only happens once, but the response stream, may be piped into other mutating/transforming/writing pipelines

Just grasping from the streams spec.. yet to decipher how everything adds up.. maybe I'll leave that to the pundits

So If I were to paraphrase my understanding till now, clone what is ideally consumed and do not bother otherwise. And in this case, the read itself, doesn't mutate the request / write it elsewhere, hence no need to clone