Last active
November 18, 2017 16:09
-
-
Save nonlogos/883d593f33e4dcfffcfe5e4c4664f0de to your computer and use it in GitHub Desktop.
Service Work with Cache API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// simple version | |
self.addEventListener('fetch', event => { | |
event.respondWith( | |
caches.match(event.request).then(response => { | |
return response || fetch(event.request); | |
}) | |
); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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