Service worker无法缓存POST请求?来我教你

440 阅读1分钟

这篇文章主要探讨的是如何通过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库:

完整代码如下:

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也可以通过修改以上代码逻辑实现。