简体   繁体   中英

Javascript - Service Workers not working correctly

I am using service workers to create an offline page for my website.

At the moment I am saving offline.html into cache so that the browser can show this file if there is no interent connection.

In the fetch event of my service worker I attempt to load index.html , and if this fails (no internet connection) I load offline.html from cache.

However, whenever I check offline mode in developer tools and refresh the page index.html still shows...

The request isn't failing, and it looks like index.html is being cached even though I didn't specify it to be.

Here is my HTML for index.html :

<!DOCTYPE html>
<html>
<head>
    <title>Service Workers - Test</title>
</head>
<body>
    <h1> Online page! </h1>
    <h3> You are connected to the internet. </h3>
</body>
<script>

if ('serviceWorker' in navigator)
{
    navigator.serviceWorker.register('service-worker.js');
}
</script>

</html>

Here is my HTML for offline.html :

<!DOCTYPE html>
<html>
<head>
    <title>You are Offline - Service Workers - Test</title>
</head>
<body>
    <h1> Welcome to the Offline Page!</h1>
    <h2> You are not connected to the internet but you can still do certain things offline. </h2>

</body>

</html>

Here is my javascript for service-worker.js :

const PRECACHE = "version1"
const CACHED = ["offline.html"];

// Caches "offline.html" incase there is no internet
self.addEventListener('install', event => {
    console.log("[Service Worker] Installed");
    caches.delete(PRECACHE)
    event.waitUntil (
        caches.open(PRECACHE)
            .then(cache => cache.addAll(CACHED))
            .then(    _ => self.skipWaiting())
    );
});

// Clears any caches that do not match this version
self.addEventListener("activate", event => {
    event.waitUntil (
        caches.keys()
            .then(keys => {
                return Promise.all (
                    keys.filter(key => {
                        return !key.startsWith(PRECACHE);
                    })
                    .map(key => {
                        return caches.delete(key);
                    })
                );
            })
            .then(() => {
                console.log('[Service Worker] Cleared Old Cache');
            })
    );
});

this.addEventListener('fetch', function(event) {
    if (event.request.method !== 'GET') return;

    console.log("[Service Worker] Handling Request ");

    // If the request to `index.html` works it shows it, but if it fails it shows the cached version of `offline.html`

    // This isn't working because `fetch` doesn't fail when there is no internet for some reason...

    event.respondWith (
        fetch(event.request)
            .then(response => {
                console.log("[Service Worker] Served from NETWORK");
                return response;
            }, () => {
                console.log("[Service Worker] Served from CACHE");
                return catches.match(event.request.url + OFFLINE_URL);
            })
    );
});

I am running a server using python's simple http server like so:

python -m SimpleHTTPServer

Does anyone know why the offline page isn't working and how I can fix this?

Thanks for the help, David

EDIT:

These images are showing that index.html (localhost) is still loading without internet which means it must be cached.

脱机复选框已打勾

本地主机状态:200(来自ServiceWorker)| service-worker.js状态:失败

Edit 2:

I've tried to add no-cache to the fetch of index.html and it still is fetching index.html when I have offline checked.

fetch(event.request, {cache: "no-cache"}) ...

David, you have two errors in one line.

Your line

return catches.match(event.request.url + OFFLINE_URL);

should be

return caches.match('offline.html');

It's catches and you haven't defined OFFLINE_URL and you don't need event request url

I tried your code and I got the same result as you in the dev tools network tab. The network tab says it loaded the index.html from service-worker, but actually the service-worker returns the cached Offline Page as expected!

显示离线页面

I think we have all forgotten how the network request works from a browser's point of view.

The issue here is, index.html is served from the disk cache when the service worker intercepts requests. browser ===> Service Worker ===> fetch event

inside the fetch event, we have ,

  • Check If there is network connectivity
    • If there is, fetch from network and respond
    • Else, fetch from cache and respond

Now, how does

If there is network connectivity, fetch from network work?

Service Worker OnFetch ===> Check in Disk Cache ===> Nothing? Fetch Online

The page being fetched here, is index.html

and the cache-control headers for index.html ,

Do Not Specify a no-cache

Hence the whole issue of the offline page not showing up.

Solution

  • Set a cache-control header with limiting values for index.html - On the server side
  • Or, add headers in the fetch request to the effect

    • pragma:no-cache
    • cache-control:no-cache

How Do I add these headers to fetch?

Apparently, fetch and the browser have their own reservations about the request body when it comes to a GET

Also, weirdness and utter chaos happens If you reuse the event.request object, for a fetch request, and add custom headers. The chaos is a list of Uncaught Exceptions due to the fetch event's request.mode attribute , which bars you from adding custom headers to a fetch when under a no-cors or a navigate mode.

Our goal is to :

Identify that the browser is truly offline and then serve a page that says so

Here's How:

Check If you can fetch a dummy html page say test-connectivity.html under your origin, with a custom cache: no-cache header. If you can, proceed, else throw the offline page

self.addEventListener( 'fetch', ( event ) => {
let headers = new Headers();
headers.append( 'cache-control', 'no-cache' );
headers.append( 'pragma', 'no-cache' );
var req = new Request( 'test-connectivity.html', {
    method: 'GET',
    mode: 'same-origin',
    headers: headers,
    redirect: 'manual' // let browser handle redirects
} );
event.respondWith( fetch( req, {
        cache: 'no-store'
    } )
    .then( function ( response ) {
        return fetch( event.request )
    } )
    .catch( function ( err ) {
        return new Response( '<div><h2>Uh oh that did not work</h2></div>', {
            headers: {
                'Content-type': 'text/html'
            }
        } )
    } ) )
} );

The {cache:'no-store'} object as the second parameter to fetch , is an unfortunate NO-OP . Just doesn't work. Just keep it for the sake of a future scenario. It is really optional as of today.

If that worked, then you do not need to build a whole new Request object for fetch

cheers!

The code piece that creates a new request is generously borrowed from @pirxpilot 's answer here

The offline worker for this specific question on pastebin

https://pastebin.com/sNCutAw7

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM