Skip to content

Instantly share code, notes, and snippets.

@nonlogos
Last active November 18, 2017 16:09
Show Gist options
  • Save nonlogos/883d593f33e4dcfffcfe5e4c4664f0de to your computer and use it in GitHub Desktop.
Save nonlogos/883d593f33e4dcfffcfe5e4c4664f0de to your computer and use it in GitHub Desktop.
Service Work with Cache API
// show cache version first and fetch new data, then swap out the new data with the cached ones
caches.match('/very-important-data.json')
.then(response => {
return response.json();
}).then(data => {
if (!didWeReceiveFreshNetworkData) {
doSomethingWithData(data);
}
}).catch(() => {
return fetchFromNetwork;
})
// simple version
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
// Great for JSON get requests
// simple version: will not work for 404 handling
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(event.request);
})
);
})
// Cache then Network
const fetchFromNetwork = fetch('/very-important-data.json')
.then(response => {
return response.json();
}).then(data => {
didWeReceiveFreshNetworkData = true;
doSomethingWithData(data);
});
// fallback image cache
const FALLBACK_IMAG_URL = 'https://localhost:3100/images/fallback-grocery.png';
const fallbackImages = 'fallback-images'; // cache name
// Precache strategy for core site assets
const ASSET_MANIFEST_URL = 'https://localhost:3000/asset-manifest.json';
const RESOURCES_TO_PRECACHE = [
/^app\.js$/,
/^web-app-manifest\.json$/,
/^img\/[\w0-9\]+(png|jpg|gif|bmp)$/
];
const CACHE_VERSION = 2;
const CACHE_PREFIX = `FEG-v${CACHE_VERSION}`;
const ALL_CACHES = {
fallbackImages: cacheName('FALLBACK_IMAGES'),
prefetch: cacheName('PREFETCH'),
fallback: cacheName('FALLBACK')
}
function cacheName(name) {
return `${CACHE_PREFIX}-${name}`;
}
export const ALL_CACHES_LIST = Object
.keys(ALL_CACHES)
.map(k => ALL_CACHES[k]);
/**
* check whether a given filename respresents a resource
* that should be precached
* @private
* @param {string} filename
* @return {boolean}
*/
function _shouldPrecacheFile(filename) {
for(let i = 0; i < RESOURCES_TO_PRECACHE.length; i++) {
if (RESOURCES_TO_PRECACHE[i].test(filename)) return true;
return false;
}
}
/**
* @return {promise}
*/
function populatePrecache() {
return fetch(ASSET_MANIFEST_URL)
.then(resp => resp.json())
.then(assetManifestJson => {
let toPreFetch = Object
.keys(assetManifestJson)
.filter(_shouldPrecacheFile)
.map(k => assetManifestJson[k]);
toPreFetch.push('/'); //precache the root index.html
return caches.open(ALL_CACHES.prefetch) //if the cache doesn't exist yet it will be created on the fly
.then(prefetchCache => {
return prefetchCache.addAll(toPreFetch);
})
});
}
function fetchImageOrFallBack(fetchEvent) {
return fetch(fetchEvent.request), {
mode: 'cors',
credentials: 'omit' // in case CORS was set to wildcard
}).then(response => {
if (!response.ok) {
return caches.match(FALLBACK_IMAG_URL, {cacheName: ALL_CACHES.fallbackimages});
}
return response;
}).catch(err => {
console.error(err);
return caches.match(FALLBACK_IMAG_URL, {cacheName: ALL_CACHES.fallbackimages});
})
}
/**
* @return {Promise<Response>}
*/
function fetchApiJsonWithFallback(fetchEvent) {
let requestURL = new URL(fetchEvent.request.url);
let acceptHeader = fetchEvent.request.headers.get('accept');
let isGroceryImage = acceptHeader.indexOf('image/*') >= 0 && requestURL.pathname.indexOf('/images/') === 0;
return caches.open(ALL_CACHES.fallback)
.then(cache => {
// try to go to the network for some json or image
// when it comes back, begin the process of putting it in the cache
// and resolve the promise with original response
// in the event that it doesn't work out
// serve from the cache
let fetchPromise = isGroceryImage
? fetchImageOrFallback(fetchEvent)
: fetch(fetchEvent.request);
return fetchPromise
.then(response => {
// Clone response so we can return one and store one
let clonedResponse = response.clone(); // response can only be handled once
// cache.put will not fetch the data again as in cache.add
cache.put(fetchEvent.request, clonedResponse);
// return the original
return response;
}).catch(() => {
return cache.match(fetchEvent.request);
})
})
}
// server worker js
const INDEX_HTML_PATH = '/';
const INDEX_HTML_URL = new URL(INDEX_HTML_PATH, self.location).toString();
self.addEventListener('install', event => {
event.waitUntil(
Promise.all([
// promise 1: get the fallback image
caches.open(fallbackImages)
.then(cache => {
cache.add(FALLBACK_IMAGE_URL);
}),
// promise 2: populate the precache stuff
populatePrecache()
])
);
});
self.addEventListener('activate', event => {
event.waitUntil(
removeUnusedCaches(ALL_CACHES_LIST)
);
});
self.addEventListener('fetch', event => {
let requestURL = new URL(event.request.url);
let isFromApi = requestURL.origin.indexOf('https://localhost:3100') >= 0;
let isHTMLRequest = event.request.headers.get('accept').indexOf('text/html') !== -1;
let isLocal = new URL(event.request.url).origin === location.origin;
// serve precached index.html
if (isHTMLRequest && isLocal) {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.match(INDEX_HTML_URL, {cacheName: ALL_CACHES_PREFETCH});
})
);
}
// you can only respond to request once
event.respondWith(
caches.match(event.request, {cacheName: ALL_CACHES.prefetch})
.then(response => {
// if a precached thing is found, go with it
if (response) return response
// otherwise,let's dig deeper
// fallback image handling or api request handling
if (isFromApi) return fetchApiJsonWithFallback(event);
// if no above caching strategy applies,
// reach out to the network request
return fetch(event.request);
})
)
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment