我试图在获取事件时在服务工作者中缓存 POST 请求。
我用了cache.put(event.request, response)
,但返回的承诺被拒绝了TypeError: Invalid request method POST.
。
当我尝试点击相同的 POST API 时,caches.match(event.request)
给了我未定义。
但是当我对 GET 方法做同样的事情时,它奏效了:caches.match(event.request)
GET 请求给了我一个响应。
服务工作线程可以缓存 POST 请求吗?如果他们不能,我们可以使用什么方法来使应用程序真正离线?
您无法使用 Cache API 缓存 POST 请求。见 https://w3c.github.io/ServiceWorker/#cache-put(第4点(。
规范存储库中有一个相关的讨论:https://github.com/slightlyoff/ServiceWorker/issues/693
一个有趣的解决方案是 ServiceWorker Cookbook 中介绍的解决方案:https://serviceworke.rs/request-deferrer.html基本上,该解决方案将请求序列化到 IndexedDB。
我在最近的一个带有 GraphQL API 的项目中使用了以下解决方案:我使用请求作为缓存键的序列化表示形式将来自 API 路由的所有响应缓存在 IndexedDB 对象存储中。然后,如果网络不可用,我使用缓存作为回退:
// ServiceWorker.js
self.addEventListener('fetch', function(event) {
// We will cache all POST requests to matching URLs
if(event.request.method === "POST" || event.request.url.href.match(/*...*/)){
event.respondWith(
// First try to fetch the request from the server
fetch(event.request.clone())
// If it works, put the response into IndexedDB
.then(function(response) {
// Compute a unique key for the POST request
var key = getPostId(request);
// Create a cache entry
var entry = {
key: key,
response: serializeResponse(response),
timestamp: Date.now()
};
/* ... save entry to IndexedDB ... */
// Return the (fresh) response
return response;
})
.catch(function() {
// If it does not work, return the cached response. If the cache does not
// contain a response for our request, it will give us a 503-response
var key = getPostId(request);
var cachedResponse = /* query IndexedDB using the key */;
return response;
})
);
}
})
function getPostId(request) {
/* ... compute a unique key for the request incl. it's body: e.g. serialize it to a string */
}
这是我使用Dexie的特定解决方案的完整代码.js作为IndexedDB包装器。随意使用它!
如果您谈论的是表单数据,那么您可以拦截 fetch 事件并以类似于下面的方式读取表单数据,然后将数据保存在 indexedDB 中。
//service-worker.js
self.addEventListener('fetch', function(event) {
if(event.request.method === "POST"){
var newObj = {};
event.request.formData().then(formData => {
for(var pair of formData.entries()) {
var key = pair[0];
var value = pair[1];
newObj[key] = value;
}
}).then( ...save object in indexedDB... )
}
})
另一种提供完整离线体验的方法可以通过使用 Cloud Firestore 离线持久性来获取。
POST/PUT请求在本地缓存的数据库上执行,然后在用户恢复其互联网连接后立即自动同步到服务器(请注意,尽管有500个离线请求的限制(。
遵循此解决方案要考虑的另一个方面是,如果多个用户具有同时同步的脱机更改,则无法保证更改将在服务器上按正确的时间顺序执行,因为 Firestore 使用先到先得的逻辑。
根据 https://w3c.github.io/ServiceWorker/#cache-put(第4点(。
if(request.method !== "GET") {
return Promise.reject('no-match')
}
尽管根据接受的答案"您无法使用缓存 API 缓存 POST 请求"......看来其实你可以。
也许有充分的理由避免这样做,作为一种例行公事,因为POST
请求的性质......但如果你必须这样做,那么这似乎是完全可能的。 就我而言,我宁愿使用GET
(在 URL 中提供GET
操作的相关信息(,但我不得不通过POST
正文发布相关信息以避免(有时(遇到 URL 长度限制。 但实际上我只是在这里使用 POST
作为解决方法,在我看来,我仍然真正将其用作GET
,因此在这种情况下我应该能够缓存对我的POST
请求的响应似乎是合理的......仅使用基于POST
正文中的内容而不是GET
URL 中的缓存键。
您所要做的就是克隆POST
请求并将其转换为GET
请求......使用克隆的GET
请求作为在服务工作进程缓存中缓存的基础,但使用原始POST
请求获取要缓存的响应。
大致如下:
if (request.method.toUpperCase() === "POST") {
// get the body text...
const body = await request.clone().text();
// create a new URL for the purposes of a cache key...
const cacheUrl = new URL(request.url);
// create an augmented URL by appending the body to the original pathname...
cacheUrl.pathname = cacheUrl.pathname + body;
// convert the request to a GET to be able to cache it...
const cacheRequest = new Request(cacheUrl.toString(), {
headers: request.headers,
method: "GET"
});
// get cache...
const cache = caches.default;
// check if there is a cached response in the cache based on the cloned GET request (for the cache key) NOT the original POST request...
let response = await cache.match(cacheRequest);
// if not, fetch the response using the original POST request...
if (!response) {
response = await fetch(request);
// put the response into the cache using the cloned GET request (for the cache key) NOT the original POST request...
event.waitUntil(cache.put(cacheRequest, response.clone()));
}
return response;
}
就我而言,我实际上并没有将克隆的请求传递到缓存 API 中,而只是传递一个字符串缓存键,因此实际上根本不需要创建一个虚拟GET
请求......我实际上只是将派生的字符串cacheUrl.pathname
直接传递给cache.match()
并cache.put()
......他们不会拒绝将其作为POST
请求,因为它只是一个字符串,而不是请求。