How To Debug An Unknown Error Occurred When Fetching The Script Service Worker
How to Gear up the Refresh Button When Using Service Workers
I'chiliad afraid yous'll have to learn the entire Service Worker API along the mode.
Dan Fabulich is a Principal Engineer at Redfin. ( We're hiring !)
In a previous commodity, I explained how and why Service Workers pause the browser'due south Refresh push button past default.
But nobody likes a whiner! In this article, I'll certificate how to set up the Refresh button.
Getting this correct requires an intimate understanding of the Service Worker lifecycle, the Caches API, the Registration API, and the Clients API. By the time you've plowed through this commodity, you'll know pretty much everything you need to know well-nigh the Service Worker API.
Hash out on Hacker News
Discuss on Reddit
Step I: Read my previous article
My previous article on refreshing Service Workers provides a lot of valuable background textile. I'll attempt to summarize it in this department, simply I think you'll probably simply have to read the whole thing kickoff, because Service Workers are rocket science.
Here's the key insight:
v1tabs tightly couple to theirv1Cache;v2tabs tightly couple to theirv2Cache. This tight coupling makes them "application caches." The app must be completely shut down (all tabs closed) in club to upgrade atomically to the newest enshroud of code.
Service Workers are like apps. You can install them, outset them, finish them, and upgrade them.
You lot tin can't safely upgrade an app (or a Service Worker) while it's still running; you need to shut the erstwhile version down start, ensuring what I telephone call "code consistency." In add-on, since new versions of an app may change the schema of client-side information, information technology'due south important to run only i version of the app at a time, ensuring "data consistency."
Service Workers can install a packet of files atomically with the          install          effect, so run data migration during the          activate          event. Past default, once a          v1          Service Worker activates, the          v2          version of the Service Worker can install but volition refuse to activate, "waiting" until the one-time          v1          Service Worker dies.
The onetime          v1          Service Worker won't die until all of the          v1          Service Worker's tabs have closed. (The Service Worker API calls tabs "clients.")
While the          v1          Service Worker lives, the Refresh button will generate pages from the old          v1          Service Worker's cache, no thing how many times we refresh.
Allow's ready this, shall we?
Four Ways to Solve the Same Problem
Here are four unlike approaches to fixing the Refresh push and forcing an update, each associated with a different phase of developer mindfulness and intellectual development.
            
          
Approach #1: We can simply skip waiting (but beware)
"For every complex trouble at that place is an answer that is clear, simple, and wrong." — H. Fifty. Mencken
          The simplest and nearly dangerous approach is to just skip waiting during installation.          There'due south a 1-line role in the global scope of all Service Workers,          skipWaiting(), that will do this for us. When nosotros employ it, the new          v2          Service Worker will immediately kill the old          v1          activated Service Worker one time the          v2          Service Worker installs.
But beware: this approach eliminates whatever guarantees of code consistency or data consistency.
The new          v2          Service Worker will actuate and delete the erstwhile          v1          Enshroud while a          v1          tab is still open up. What happens if the          v1          tab tries to load          /v1/styles.css          at that point?
At best, the          v1          tab will attempt to load our style canvas from the network, which is a waste of bandwidth, because we just discarded a perfectly proficient cached re-create of the file. At worst, the user might be offline at the time, resulting in a broken          v1          tab, but the user won't know why.
The worst thing nigh blindly calling          skipWaiting()          is that it appears to work perfectly at first, but it results in bugs in product that are hard to understand and reproduce.
Approach #2: Refresh onetime tabs when a new Service Worker is installed
This is just a slight tweak on the previous approach.          navigator.serviceWorker          is a ServiceWorkerContainer object; it has a          controllerchange          event which fires when a new Service Worker takes control of the current page.
So suppose we          skipWaiting()          during installation and add lawmaking like this in the web folio:
          navigator.serviceWorker.addEventListener('controllerchange',
            function() { window.location.reload(); }
);                That does work. When the          v2          Service Worker skips waiting to have control, any          v1          tab will automatically refresh, turning it into a          v2          tab.
But it's not a proficient user experience to trigger a refresh when the user's not expecting it. If the folio refreshes while the user is filling out an lodge form for styptic pencils, yous could lose data, coin, and/or blood. 💔
UPDATE: If you use this technique, use a variable to make sure yous refresh the page only one time, or you'll cause an infinite refresh loop when using the Chrome Dev Tools "Update on Reload" feature.
          var refreshing;
navigator.serviceWorker.addEventListener('controllerchange',
            function() {
            if (refreshing) return;
            refreshing = true;
            window.location.reload();
            }
);                Arroyo #3: Allow the user to control when to skip waiting with the Registration API
In this approach, nosotros'll wait for a new Service Worker to install, and and then we'll pop upwards a UI message in the page, prompting the user to click an in-app "Refresh" link.
              
            
We'll even so configure the page to refresh when the          controllerchange          event fires, as in Approach #2 — but in this approach, the event will burn when the user          asks          for a refresh, and so we know the user won't be interrupted.
Oh, joy, another Service Worker API: Registration API
To listen for a new Service Worker, we'll need to use a ServiceWorkerRegistration object. In our "naive" endeavor to hook up a Service Worker, we called          navigator.serviceWorker.register()          to register our Service Worker, merely we didn't do anything with the returned value. It turns out that          annals()          returns a Hope for a "registration."
In that location are a bunch of nifty things we tin can do with a registration, plus a handful of useful things we tin can practice with the          navigator.serviceWorker          ServiceWorkerContainer object that I haven't mentioned yet.
A few things you lot can do with a registration:
- Call            
update()on the registration to manually refresh the Service Worker script. The Service Worker script automatically attempts to refresh when nosotros callnavigator.serviceWorker.register(), simply we might desire to poll for updates more than frequently than that, e.1000. once an hour or something. - Telephone call            
unregister()on the registration to terminate and unregister the Service Worker. - Call            
navigator.serviceWorker.getRegistration()to become a Promise for the existing registration (which may be null). - Get a reference to the agile ServiceWorker object (the ServiceWorker object that controls the current folio) via the            
navigator.serviceWorker.controllerholding or the registration'due south.activeproperty. - We can also use a registration to go a reference to a            
.waitingService Worker or an.installingService Worker. - One time we have a ServiceWorker, nosotros tin can call            
postMessage()to transport information to the Service Worker. Within the Service Worker script, nosotros can heed for amessageMessageEvent, which has a.informationproperty containing the message object. 
Finally, the actual implementation
We get-go by adding code to the Service Worker'due south script to listen for a posted bulletin:
          addEventListener('message', messageEvent => {
            if (messageEvent.data === 'skipWaiting') return skipWaiting();
});                Next, we'll demand to listen for a          .waiting          ServiceWorker, which requires some inconvenient boilerplate code. The registration fires an          updatefound          event when in that location's a new          .installing          ServiceWorker; the          .installing          ServiceWorker fires a          statechange          event once it's in the "installed" waiting land.
Here's the boilerplate. Note that since this code runs in the web page, and not in the Service Worker script, nosotros've written it in ES5 JavaScript.
          office listenForWaitingServiceWorker(reg, callback) {
            function awaitStateChange() {
            reg.installing.addEventListener('statechange', role() {
            if (this.state === 'installed') callback(reg);
            });
            }
            if (!reg) render;
            if (reg.waiting) render callback(reg);
            if (reg.installing) awaitStateChange();
            reg.addEventListener('updatefound', awaitStateChange);
}                We'll listen for a waiting Service Worker, then prompt the user to refresh. When the user requests a refresh, nosotros'll mail a message to the waiting Service Worker, asking it to          skipWaiting(), which will fire the          controllerchange          event, which we've configured to refresh the page.
// reload in one case when the new Service Worker starts activating
var refreshing;
navigator.serviceWorker.addEventListener('controllerchange',
role() {
if (refreshing) return;
refreshing = true;
window.location.reload();
}
); office promptUserToRefresh(reg) {
// this is just an example
// don't utilize window.ostend in real life; it's terrible
if (window.confirm("New version available! OK to refresh?")) {
reg.waiting.postMessage('skipWaiting');
}
} listenForWaitingServiceWorker(reg, promptUserToRefresh);
Note that this Arroyo #iii works even if there are multiple open tabs. All of the tabs will display the "update available" prompt; when the user clicks the "refresh" link in whatsoever one of them, the new Service Worker will take control of all of the tabs, causing all of them to refresh to the new version.
However, Arroyo #iii has 2 major drawbacks.
First, this approach is hella complicated. Maybe you can re-create and paste this average, but you won't be able to debug it unless you truly grok information technology, which requires an intimate agreement of the Registration API, the Service Worker life bike, and how to exam for the problems resulting from code/information inconsistency.
Despite the complexity, Arroyo #3 seems to exist the standard recommended approach. Google has put together a Udacity course on Service Workers, and this is the arroyo that they certificate. (It occupies a meaning fraction of the course, and includes 3 sub-quizzes building on one another. 😭)
I wish this approach were easier; there are a couple of tickets on the Github repository where these things are managed. Specifically, I wish it were easier to listen for a waiting Service Worker, and I wish it were possible to make a Service Worker skip waiting without posting a message to information technology.
Imagine if this whole section were replaced past a snippet like this:
office promptUserToRefresh() {
// don't use confirm in production; this is but an case
if (confirm('Refresh now?')) reg.waiting.skipWaiting();
} if (reg.waiting) promptUserToRefresh();
reg.addEventListener('statechange', function(e) {
if (due east.target.state === 'installed') {
promptUserToRefresh();
} else if (east.target.state === 'activated') {
window.location.reload();
}
});
Second, this approach does not and cannot fix the browser's built-in Refresh button. To refresh the page, users have to use an in-app Refresh link instead.
When the user actually has multiple tabs open, that might be for the best. Nosotros need the user'due south browser to refresh all of our app's tabs at in one case in gild to restart the "app" and ensure data consistency. But when there'south just ane tab remaining, we can do better.
Approach #4: Skip waiting when the last tab refreshes with the Clients API (buggy in Firefox)
The Request object has a          .manner          property. Information technology'south mostly used for managing the security restrictions around the same-origin policy and Cross-Origin Resources Sharing (CORS) with settings similar:          same-origin,          cors, and          no-cors. But it has one more style that'southward not similar the others:          navigate.
When a Service Worker'south fetch listener sees a request in          navigate          mode, and so nosotros know that a new document is being loaded. At that point, we can count the currently running tabs ("clients") with the Clients API.
Oh yes, my friends, information technology's time to learn yet another new API.
Using the global          clients          Clients object, we can get a list of Client objects. Each Client has an          id          property, corresponding to each FetchEvent'southward          .clientId          property.
Only we don't really demand to do anything with the individual clients or their IDs; we just need to          count          them. From at that place, we tin can use the          registration          object in the global scope of the Service Worker to run into if there'south a Service Worker waiting, and mail service it a message asking information technology to skip waiting in that case.
          if (upshot.request.mode === 'navigate' && registration.waiting) {
            if ((wait clients.matchAll()).length < 2) {
            registration.waiting.postMessage("skipWaiting");
            }
}                There are two bug with this snippet. Start, as of November 2017, this technique works nifty in Chrome 62, just it doesn't work in Firefox.          registration.waiting          is always cipher from inside the Service Worker. Hopefully the Firefox team volition sort this out before long.
There'southward an another, deeper trouble. The navigation has already started, and the old          v1          Service Worker is already treatment information technology. If the          v1          Service Worker handles the request in the normal fashion with          caches.match(), information technology will respond to the navigation request with a response from the one-time          v1          Service Worker'south enshroud (considering          caches.match()          ever prefers to friction match from the          oldest          bachelor cache), but the refreshed page will then endeavour to load all of its scripts and styles from the new          v2          cache, blatantly violating lawmaking consistency.
In this case, nosotros'll return a blank response that instantly refreshes the page, instead. We'll utilise the HTTP          Refresh          header to refresh the page after 0 seconds; the page will appear bare for an instant, then refresh itself as a          v2          tab.
          addEventListener('fetch', issue => {
            result.respondWith((async () => {
            if (event.request.mode === "navigate" &&
            effect.asking.method === "GET" &&
            registration.waiting &&
            (look clients.matchAll()).length < 2
            ) {
            registration.waiting.postMessage('skipWaiting');
            return new Response("", {headers: {"Refresh": "0"}});
            }
            return await caches.match(event.request) ||
            fetch(effect.asking);
            })());
});                With this code in place, the browser'south Refresh button does what it's "supposed" to practice when we're using just 1 tab.
I recommend combining this technique with the Approach #three in-app "refresh" link described earlier, to handle the case where multiple tabs demand to be updated, and to handle Firefox's buggy implementation of          registration.waiting.
Whoa! You Know Service Workers Now!
Typical Service Worker tutorials like the "Using Service Workers" guide on MDN introduce only the raw basics (equally of November 2017): a uncomplicated          install          listener that caches a list of URLs in a versioned Enshroud, a          fetch          listener, and an          activate          listener to elapse obsolete Caches.
But as you've read this article and the previous article, you lot've learned near almost all of the other classes in the ServiceWorker API:
- Cache
 - CacheStorage (
caches) - Client
 - Clients (
clients) - ExtendableEvent
 - FetchEvent
 - InstallEvent
 - Navigator.serviceWorker (the ServiceWorkerContainer)
 - ServiceWorker (especially its            
postMessage()method) - ServiceWorkerGlobalScope
 - ServiceWorkerRegistration
 
As of 2017, there are other features associated with Service Workers (Background Sync and Push Notifications), but those APIs are a piece of cake compared to the nightmare you've been through to set up the Refresh button.
So, congratulations on getting through all of this material! You're pretty much a Service Worker wizard at this bespeak.
Discuss on Hacker News
Discuss on Reddit
            
          
P.South. Redfin is hiring .
Source: https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68
Posted by: lopezhithatides88.blogspot.com

0 Response to "How To Debug An Unknown Error Occurred When Fetching The Script Service Worker"
Post a Comment