这篇文章主要探讨的是如何通过Service worker来缓存POST请求
前言
- service worker 默认使用cache storage来储存接口信息,而cache storage 只支持 GET 请求,否则会出现报错
TypeError: Request method 'POST' is unsupported
。 - Workbox是 GoogleChrome 团队推出的一套基于Service worker的解决方案,由于Cache Storage API的限制,无法缓存 POST 请求。
方案
由于cache storage不支持缓存 POST 请求,我们使用 IndexedDB 来存储缓存的 JSON,具体流程如下:
- Service Worker 拦截一个 POST 请求,并根据request中的查询字符串组成一个 MD5 加密的缓存key。
- Service Worker 使用缓存key将新的 JSON 响应存储在 IndexedDB 中。
- 如果POST请求失败,Service Worker 使用缓存key检查 IndexedDB。如果密钥存在,则返回缓存的 JSON。
这里我们会使用到两个js库:
- IDB-Keyval,用于存储键/值对
- CryptoJS,用于加解
完整代码如下:
importScripts('https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js');
importScripts('https://cdn.jsdelivr.net/npm/idb-keyval@3/dist/idb-keyval-iife.min.js
const store = new idbKeyval.Store('POST-Resp-Cache', 'PostResponses');
// NetworkFirst 策略
async function networkFirst(event) {
let cachedResponse = await getCache(event.request.clone());
let fetchPromise = fetch(event.request.clone())
.then(response => {
setCache(event.request.clone(), response.clone());
return response;
})
.catch(err => {
console.error(err);
return cachedResponse;
});
return fetchPromise;
}
// 序列化请求
async function serializeResponse(response) {
let serializedHeaders = {};
for (const entry of response.headers.entries()) {
serializedHeaders[entry[0]] = entry[1];
}
let serialized = {
headers: serializedHeaders,
status: response.status,
statusText: response.statusText,
};
serialized.body = await response.json();
return serialized;
}
// 设置cache
async function setCache(request, response) {
let body = await request.json();
let id, entry;
id = CryptoJS.MD5(request.url).toString();
entry = {
response: await serializeResponse(response),
timestamp: Date.now(),
};
idbKeyval.set(id, entry, store);
}
// 获取cache
async function getCache(request) {
let data, body, id;
try {
id = CryptoJS.MD5(request.url).toString();
data = await idbKeyval.get(id, store);
if (!data) return null;
// Check cache max age.
let cacheControl = request.headers.get('Cache-Control');
let maxAge = cacheControl ? parseInt(cacheControl.split('=')[1]) : 3600;
if (Date.now() - data.timestamp > maxAge * 1000) {
console.log(`Cache expired. Load from API endpoint.`);
return null;
}
console.log(`Load response from cache.`);
return new Response(JSON.stringify(data.response.body), data.response);
} catch (err) {
return null;
}
}
如果要实现Stale-While-Revalidate
,或者Cache First
也可以通过修改以上代码逻辑实现。