ERR_FAILED when service worker loads new page, why? - javascript

I've written a service worker with help from a tutorial:
var CACHE = 'cache-and-update';
self.addEventListener('install', function (evt) {
console.log('The service worker is being installed.');
evt.waitUntil(precache());
});
self.addEventListener('fetch', function (evt) {
evt.respondWith(fromCache(evt.request));
evt.waitUntil(update(evt.request));
});
function precache() {
return caches.open(CACHE).then(function (cache) {
return cache.addAll([
// Nothing.
]);
});
}
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
return matching || Promise.reject('no-match');
});
});
}
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response);
});
});
}
It always serves from the cache first, then fetches all files, and updates on page reload.
The service worker is registered like this in every HTML file on my server:
<script>
navigator.serviceWorker.register('https://www.example.com/sw.js', {
scope: '../'
});
</script>
Now the problem is, when I go to a page that isn't cached yet, it first shows me the default Chrome ERR_FAILED error (and the 'no-match' promise rejection).
The sw then fetches it anyway, while showing the error page to the client, and on a reload it works again (because it's served from the cache).
Why is this happening and how can I make the service worker load the page from the server when there's no cached version available?

You got the fetch listener wrong here,
You are rejecting the promise if the file was not found in the cache, you should fetch it then cache it instead of returning Promise.reject('no-match') and you do not need the evt.waitUntil in this case
here is a full snippet to a working service worker. If a request doesn't match anything in the cache, we get it from the network, send it to the page and add it to the cache at the same time.
let cacheName = 'cache-v1';
self.addEventListener('install', (e) => {
let cache = caches.open(cacheName).then((c) => {
c.addAll([
// nothing
]);
});
e.waitUntil(cache);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.open(cacheName).then(function (cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function (response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});

Related

Catch request in service worker and respond with different image url

I have this fetch function in my service worker and i try to respond with a different image when the pwa is offline. The fetch function inside the catch throws: Uncaught (in promise) TypeError: Failed to fetch.
self.addEventListener('fetch', (e) => {
if (e.request.method === 'GET') {
e.respondWith(
caches.match(e.request).then((cachedResponse) => {
return cachedResponse || fetch(e.request.url)
.then((res) => {
return res;
}).catch(() => {
//if it is a image then
let url1 = e.request.url.replace(".jpg", "_mini.jpg");
return fetch(url1);
/* Or like this
caches.match(url1).then((cResp) => {
return cResp;
})
*/
})
})
)
}
});
Is it not possible to catch an error when you are offline and respond with a "mini" alternativ image or what do i do wrong?
You are returning cachedResponse if it exists, that prevents your replacement logic from being reached. Try to remove cachedResponse ||.
This can be removed as well:
.then((res) => {
return res;
})
Also, if you're offline the return fetch(url1); won't work because the fetch from a Service Worker won't trigger another FetchEvent recursively. So you have to return caches.match(url1) instead, implying it has been cached before.

Service worker run event waitUntil() based on some condition

I am trying to implement a cache-then-network strategy in the service worker which updates the cache in the background. I want to avoid unnecessary fetch requests, so came up with the following solution -
function cache_then_network(event) {
var updated = false;
event.respondWith(
caches.open(staticCacheName)
.then(cache => cache.match(event.request)
.then((response) => {
if (response) {
return response;
}
else {
return fetch(event.request)
.then((response) => {
const resClone = response.clone();
return caches.open(staticCacheName)
.then((cache) => {
cache.put(event.request, response);
updated = true;
return resClone;
})
})
}
})
)
)
if (!updated) {
event.waitUntil(update(event.request))
}
}
The update function updates the cache by fetching the request using the network.The issue is that the updated variable is always false, causing the update function to run everytime.
I'm not well versed with service workers, and the code is basically stitched up from multiple sources. So alternative/better solutions are welcome. My ultimate goal is to cache first, fetch from network in background, and set a flag which tells whether the content has changed or not.
The Service worker offline-cookbook has all the answers -
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
return response || fetchPromise;
})
})
);
});

How to wait for socket connect within rest request?

(This is my first ever contact with Javascript. ...)
Hi, I have to write a rest api with Express, which is no problem due to the huge amount of examples everywhere.
Though, within a rest request I've to contact another tcp server (for modbus). So, within such a request I've to wait for the connect/ready event from socket.connect(). What's a proper way to do that?
What I came up with is encapsulating the socket.connect() in a Promise. The stripped code looks like:
function get_holding() {
const socket = new net.Socket();
const client = new modbus.client.TCP(socket); /* = require('jsmodbus') */
return new Promise(function (resolve, reject) {
// fulfill the Promise, proceed in the chain
socket.on('ready', function () { resolve(); });
// close connection after receiving the response
socket.on('data', function () { socket.end(); });
socket.on('error', function (error) { reject(error); });
socket.connect(/* ip and port */);
})
.then(function () {
// could be above in socket.on('ready', ...),
// but splitting looks better
return client.readHoldingRegisters(0, 2);
})
.then(function (response) {
// process response (code stripped)
return /* processed values */;
})
.catch(function (error) {
throw error;
});
}
function controller_get_holding(req, res) {
get_holding()
.then(function (values) {
res.json(values);
})
.catch(function (error) {
res.send(require('util').inspect(arguments, {depth: null}));
});
}
...
app.route('/holding')
.get(controller_get_holding);
Is that the way to go?
Just found promise-socket, which is all I need.

how to delete service worker cache on localhost

please see the code of service worker below:
var CACHE_NAME = 'my-site-cache-v18';
var urlsToCache = [
'1.jpg',
'2.png'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
The issue I am facing is in the inspect element of chrome the cache storage graph is continue growing
and when I see the the cache storage in file explorer old folders are not deleted. every time i refresh the page creates a new folder.
these encrypted folders are increasing every time I change the "CACHE_NAME" (the verion of the cache).
Please Help. I tried a lot but unable to solve it.
To use the filter(), the arguement function you are passing in the filter() does not return anything, whereas you need to return a cacheName value from the function.
cacheNames.filter(function(cacheName) {
//you need to return the cachename which you want to
//delete from the cache by specifying the conditions
}).map(function(cacheName) {
return caches.delete(cacheName);
})
for example-
var staticCacheName = 'my-site-v18';
self.addEventListener('install', function(event) {
var urlsToCache = [
'1.jpg',
'2.jpg'
];
event.waitUntil(
caches.open(staticCacheName).then(function(cache){
return cache.addAll(urlsToCache);
}))
});
self.addEventListener('activate',function(event){
console.log('activating');
event.waitUntil(
caches.keys().then(function(cacheNames){
console.log(cacheNames);
return Promise.all(
cacheNames.filter(function(cacheName){
return cacheName.startsWith('my-site') && cacheName != staticCacheName ;
}
).map(function(cacheName){
return caches.delete(cacheName);
})
);
})
);
});
you might need to change your activation event and try another approach.
i use it like that:
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== cacheName) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
Does the cache usage grow every time you reload the page?
This may be due to a bug in chrome. It has been fixed and looks like it will go out in v65.
https://bugs.chromium.org/p/chromium/issues/detail?id=801024

Service Worker get from cache then update cache

I'm using the following logic in my service worker (in my own words):
If cache exists, use it, but also update cache from the network for later
event.respondWith( // on `fetch`
caches.open(CACHE)
.then(function(cache) {
return cache.match(request);
})
.then(function(matching) {
if (matching) {
requestAndUpdateCache(event);
return matching;
}
...
In addition to responding with the cached response, I also run this function called requestAndUpdateCache.
function requestAndUpdateCache(event){
var url = event.request.url + '?t=' + new Date().getTime();
fetch(url)
.then(function(response){
if (response && response.status === 200){
caches.open(CACHE)
.then(function(cache){
cache.put(event.request, response.clone());
});
}
}, function(error){
console.log(error);
});
}
Questions: Does this function and its placement make sense to accomplish the logic outlined above?
What you're describing is a stale-while-revalidate strategy.
The canonical place to look for implementations of different service worker caching strategies is Jake Archibald's The Offline Cookbook. There's a section that covers stale-while-revalidate, including the following code:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
return response || fetchPromise;
})
})
);
});

Categories

Resources