Sharing fetch handler logic defined across multiple service workers

冷暖自知 提交于 2021-02-04 16:28:26

问题


Following This Discussion, Where there is a comment that speaks of

patching a Fetch

by overriding self.fetch, self.XMLHttpRequest, and self.caches (for cache.add/addAll)? It would seem these would let you intercept network requests and manipulate responses before the main SW script sees them.

I have been looking out for any documentation that speaks of such stuff, and can not seem to find any.

In a scenario where I simply need multiple service workers to co exist in a single scope,

After having an importScripts in one of them to import the event handlers of the other,

How exactly would I patch a fetch / avoid a fetch race / have both the fetch handlers work?


回答1:


There's a few things to cover here:

Multiple Service Workers in a Single Scope

There can only be one active service worker for a given scope. If you attempt to register two different service worker scripts that each have the same scope, the second registration will trigger the service worker update flow:

// There's an implied default scope of '/'.
// See https://stackoverflow.com/a/33881341/385997
navigator.serviceWorker.register('/sw1.js');

// If called later on, this will trigger the update flow.
// You'll only end up with one of the two being active.
navigator.serviceWorker.register('/sw2.js');

The exact timing for when sw2.js will activate and take control over any existing clients depends on whether you're using self.skipWaiting() and self.clients.claim() inside of sw2.js. Once sw2.js activates, sw1.js will be marked as redundant.

Another way of asking what I think is the same question is whether you can have multiple service workers controlling the same client page at the same time. The answer is no, you can have at most one service worker controlling any client page, and only that service worker will be able to respond to fetch events originating from the page.

Using importScripts to Share Common Handlers

Instead of attempting to register multiple service workers with the same scope, using importScripts() to pull in logic that's defined in a different JavaScript file sounds like a reasonable approach. There are a few things to keep in mind when using importScripts() in this fashion:

  • importScripts() needs to be called during the initial startup execution of your service worker code, not inside an event handler. I.e. "lazy-loading" of importScripts() is not supported.
  • importScripts() executes all the of the code inside of the file(s) synchronously, one by one, in the order in which they're listed. You can have multiple importScripts(), or importScripts() inside of files that are themselves imported, and they'll all execute in a defined order.
  • Inside an imported script, self will be set to the same ServiceWorkerGlobalScope that would be used if the code were in the top-level service worker. I.e., there's no difference between calling self.addEventListener() inside of an imported script or inside of the top-level service worker.
  • (This isn't directly related to your question, but it's good to know:) The files referenced via importScripts() will be cached by default, using the same mechanism that's built in to the browser for caching your top-level service worker file. While there are some changes to the service worker specification underway to change this, as of right now, those cached importScripts() files will be used indefinitely as long as their filenames don't change. So a best practice is to either include a version number or a hash in the file names of anything referenced with importScripts().

Multiple fetch Event Handlers

What happens when you have multiple calls to self.addEventListener('fetch')?

From the previous section we know that it's not relevant whether those multiple calls originate inside of an importScripts() resource or the top-level service worker. They both operate on the same global scope.

The behavior is well-defined: when a client page makes a request, it will trigger the fetch handlers of the controlling service worker one by one, in the order in which they were registered, until the first call is made to event.respondWith(). One one fetch event handler calls respondWith(), no other fetch event handlers will be triggered, and it's the sole responsibility of that handler to (eventually) return a Response to the client page.

Since the order in which your self.addEventlistener('fetch') calls matter, make sure that you list the files in your importScripts() in an appropriate order, and either include your call to importScripts() before or after you define any fetch event handlers in your top-level service worker, depending on which you want to take precedence.

While you can use conditional logic to determine whether or not to call event.respondWith(), that logic can't be asynchronous, since the service worker won't wait to see whether event.respondWith() is called. It needs to synchronously move on to the next event handler (assuming there is one).

So inside a fetch handler, you can use conditional logic like

// This can be executed synchronously.
if (event.request.url.endsWith('.html')) {
  event.respondWith(...);
}

but you can't use conditional logic like:

// caches.match() is asynchronous, and the service worker will have
// moved on to the next `fetch` handler before it completes.
caches.match('index.html').then(response => {
  if (response) {
    event.respondWith(...);
  }
});

There's a live code sample that you can explore if you want to see the multiple-handlers behavior for yourself.



来源:https://stackoverflow.com/questions/45257602/sharing-fetch-handler-logic-defined-across-multiple-service-workers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!