https://jakearchibald.com/2014/offline-cookbook/
在install中对依赖进行缓存
self.addEventListener('install', function(event) { event.waitUntil( caches.open('mysite-static-v3').then(function(cache) { return cache.addAll([ '/css/whatever-v3.css', '/css/imgs/sprites-v6.png', '/css/fonts/whatever-v8.woff', '/js/all-min-v4.js' // etc ]); }) ); });
waitUntil接收的参数是一个promise,这个promise决定了install阶段的持续时长和是否成功。
在install中对非依赖进行缓存
self.addEventListener('install', function(event) { event.waitUntil( caches.open('mygame-core-v1').then(function(cache) { cache.addAll( // levels 11-20 ); return cache.addAll( // core assets & levels 1-10 ); }) ); });
以上waitUntil接收到的promise,仅仅是对应加载了核心的缓存。虽然也加载了非关键的缓存(levels11-20),但是对install的状态无影响。
在activate阶段清除缓存
self.addEventListener('activate', function(event) { event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.filter(function(cacheName) { // Return true if you want to remove this cache, // but remember that caches are shared across // the whole origin }).map(function(cacheName) { return caches.delete(cacheName); }) ); }) ); });
Keep your activation as lean as possible, only use it for things you couldn't do while the old version was active
处理用户的交互
给用户一个按钮,让用户选择保存某些资源以离线访问。资源的唯一标识与某个cache要一一对应
document.querySelector('.cache-article').addEventListener('click', function(event) { event.preventDefault(); var id = this.dataset.articleId; caches.open('mysite-article-' + id).then(function(cache) { fetch('/get-article-urls?id=' + id).then(function(response) { // /get-article-urls returns a JSON-encoded array of // resource URLs that a given article depends on return response.json(); }).then(function(urls) { cache.addAll(urls); }); }); });
以上代码与sw是否被激活无关。也就是说cache和sw的运行是分开的两部分。
拦截网络请求
当一个请求的响应不在cache中,就从网络获取,显示到界面上,最后保存到cache中。
self.addEventListener('fetch', function(event) { event.respondWith( caches.open('mysite-dynamic').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; }); }); }) ); });
以上的clone是为了让页面对response的读取与cache对response的读取分离开来,互不影响。
更新cache
总是先更新缓存
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; }) }) ); });
消息推送
后台同步
只使用缓存
self.addEventListener('fetch', function(event) { // If a match isn't found in the cache, the response // will look like a connection error event.respondWith(caches.match(event.request)); });
只使用网络
self.addEventListener('fetch', function(event) { event.respondWith(fetch(event.request)); // or simply don't call event.respondWith, which // will result in default browser behaviour });
缓存优先,后访问网络
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); })
访问缓存和访问网络同时开始
对于某些情况,古老的硬盘+病毒扫描软件+快速的网络。访问网络的速度可能比访问硬盘更快。
self.addEventListener('fetch', function(event) { event.respondWith( promiseAny([ caches.match(event.request), fetch(event.request) ]) ); });
访问网络失败后才取缓存
self.addEventListener('fetch', function(event) { event.respondWith( fetch(event.request).catch(function() { return caches.match(event.request); }) ); });
用于资源经常更新的站点,当离线访问时才使用旧的资源。
访问缓存后再访问网络
var networkDataReceived = false; startSpinner(); // fetch fresh data var networkUpdate = fetch('/data.json').then(function(response) { return response.json(); }).then(function(data) { networkDataReceived = true; updatePage(); }); // fetch cached data caches.match('/data.json').then(function(response) { if (!response) throw Error("No data"); return response.json(); }).then(function(data) { // don't overwrite newer network data if (!networkDataReceived) { updatePage(data); } }).catch(function() { // we didn't get cached data, the network is our last hope: return networkUpdate; }).catch(showErrorMessage).then(stopSpinner);
generic fallback
self.addEventListener('fetch', function(event) { event.respondWith( // Try the cache caches.match(event.request).then(function(response) { // Fall back to network return response || fetch(event.request); }).catch(function() { // If both fail, show a generic fallback: return caches.match('/offline.html'); // However, in reality you'd have many different // fallbacks, depending on URL & headers. // Eg, a fallback silhouette image for avatars. }) ); });
模板
importScripts('templating-engine.js'); self.addEventListener('fetch', function(event) { var requestURL = new URL(event.request); event.respondWith( Promise.all([ caches.match('/article-template.html').then(function(response) { return response.text(); }), caches.match(requestURL.path + '.json').then(function(response) { return response.json(); }) ]).then(function(responses) { var template = responses[0]; var data = responses[1]; return new Response(renderTemplate(template, data), { headers: { 'Content-Type': 'text/html' } }); }) ); });
大概意思就是app shell缓存在本地,然后请求动态数据,两者渲染,得出结果。
将以上的技巧组合到一起
self.addEventListener('fetch', function(event) { // Parse the URL: var requestURL = new URL(event.request.url); // Handle requests to a particular host specifically if (requestURL.hostname == 'api.example.com') { event.respondWith(/* some combination of patterns */); return; } // Routing for local URLs if (requestURL.origin == location.origin) { // Handle article URLs if (/^/article//.test(requestURL.pathname)) { event.respondWith(/* some other combination of patterns */); return; } if (/.webp$/.test(requestURL.pathname)) { event.respondWith(/* some other combination of patterns */); return; } if (request.method == 'POST') { event.respondWith(/* some other combination of patterns */); return; } if (/cheese/.test(requestURL.pathname)) { event.respondWith( new Response("Flagrant cheese error", { status: 512 }) ); return; } } // A sensible default pattern event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); });
完。