在PWA应用中,最为重要功能之一就是缓存了。也即将一些静态资源缓存在本地,便于下次快速获取到,而不用从网络重新获取。这可以改善加载速度和节省网页浏览。所以,这个功能应该属于所有网站的一项必备能力。
Storage Options
首先来总结一下存储策略。主要有下面这么多种:
- IndexedDB(原生API过于复杂)
- Web SQL
- Local Storage
- Cache Storage API
- pouchDB(Based on CouchDB)
- localForage(自动帮你选择IndexedDB,Web SQL,Local Storage)
- Lovefield
Cache Storage API
- caches.open()
- caches.has()
- caches.delete()
Service Worker Cache Example
注册Service Worker代码
// Progressive Enhancement (SW supported)
// if ('serviceWorker' in navigator) {
if (navigator.serviceWorker) {
// Register the SW
navigator.serviceWorker.register('/sw.js').then((registration) => {
}).catch(console.log);
}
Service Worker sw.js
// Service Worker
const pwaCache = 'pwa-cache-2';
self.addEventListener('install', (e) => {
let cacheReady = caches.open(pwaCache).then((cache) => {
console.log('New cache ready.');
return cache.addAll([
'/',
'style.css',
'thumb.png',
'main.js'
]);
});
e.waitUntil(cacheReady);
});
self.addEventListener('activate', (e) => {
let cacheCleaned = caches.keys().then((keys) => {
keys.forEach((key) => {
if( key !== pwaCache ) return caches.delete(key);
});
});
e.waitUntil(cacheCleaned);
});
// 此处可以使用下面任意一个Cache策略
self.addEventListener('fetch', (e) => {
// Skip for remote fetch
if ( !e.request.url.match(location.origin) ) return;
// Serve local fetch from cache
let newRes = caches.open(pwaCache).then((cache) => {
return cache.match(e.request).then((res) => {
// Check request was found in cache
if (res) {
console.log(`Serving ${res.url} from cache.`);
return res;
}
// Fetch on behalf of client and cache
return fetch(e.request).then((fetchRes) => {
cache.put(e.request, fetchRes.clone());
return fetchRes;
});
});
});
e.respondWith(newRes);
});
Caching Strategies
总结一下客户端缓存的集中策略。
1. Cache Only
只使用Cache,如果Cache中没有该资源,或者Cache被清理,则会失败。这仅使用与纯本地应用。
// 1. Cache only. Static assets - App Shell
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request));
})
2. Cache with Network Fallback
适用于存储静态资源,对实时性要求不高的资源。例如图片,CSS等。
self.addEventListener('fetch', (e) => {
// 2. Cache with Network Fallback
e.respondWith(
caches.match(e.request).then( (res) => {
if(res) return res;
// Fallback
return fetch(e.request).then( (newRes) => {
// Cache fetched response
caches.open(pwaCache).then( cache => cache.put(e.request, newRes) );
return newRes.clone();
})
})
);
})
3. Network with cache fallback
在慢速网络下,不一定是一个好的方案。
self.addEventListener('fetch', (e) => {
e.respondWith(
fetch(e.request).then( (res) => {
// Cache latest version
caches.open(pwaCache).then( cache => cache.put(e.request, res) );
return res.clone();
// Fallback to cache
}).catch( err => caches.match(e.request) )
);
})
4. Cache with Network Update
在Workbox中该策略被称为Stale-While-Revalidate
,The stale-while-revalidate pattern allows you to respond the request as quickly as possible with a cached response if available, falling back to the network request if it’s not cached. The network request is then used to update the cache.
// 4. Cache with Network Update
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.open(pwaCache).then( (cache) => {
// Return from cache
return cache.match(e.request).then( (res) => {
// Update
let updatedRes = fetch(e.request).then( (newRes) => {
// Cache new response
cache.put(e.request, newRes.clone());
return newRes;
});
return res || updatedRes;
})
})
);
})
5. Cache & Network Race with offline content
self.addEventListener('fetch', (e) => {
// 5. Cache & Network Race with offline content
let firstResponse = new Promise((resolve, reject) => {
// Track rejections
let firstRejectionReceived = false;
let rejectOnce = () => {
if (firstRejectionReceived) {
if (e.request.url.match('thumb.png')) {
resolve(caches.match('/placeholder.png'));
} else {
reject('No response received.')
}
} else {
firstRejectionReceived = true;
}
};
// Try Network
fetch(e.request).then( (res) => {
// Check res ok
res.ok ? resolve(res) : rejectOnce();
}).catch(rejectOnce);
// Try Cache
caches.match(e.request).then( (res) => {
// Check cache found
res ? resolve(res) : rejectOnce();
}).catch(rejectOnce);
});
e.respondWith(firstResponse);
})
要想写一个完备的缓存策略,并不是那么容易。以上的缓存策略,仅仅作为参考和学习。那些代码不适合在生产环境中直接使用。如果想再生产环境中实现这项功能,最好使用Workbox。