# 1 引言

`caches` 这个 API 是针对 `Request` `Response` 的。`caches` 一般结合 `Service Worker` 使用，因为请求级别的缓存与具有页面拦截功能的 `Service Worker` 最配。

本周精读的文章是 [cache-api](https://bitsofco.de/cache-api-101/)，介绍了浏览器缓存接口的基本语法。

# 2 概述

浏览器拥有全局变量 `caches` 操作缓存。

`caches` 包含任意命名空间，可以通过 `caches.open` 创建或访问。

```typescript
const myCache = await caches.open("myCache");
```

## 添加缓存

通过 `add` 添加缓存。由于 `caches` 缓存是基于请求的，因此参数可以是一个 URL 地址，或一个完整的 Request 对象：

```typescript
// URL only
myCache.add("/subscribe");

// Full request object
myCache.add(new Request('/subscribe', {
    method: "GET",
    headers: new Headers({
    'Content-Type': 'text/html'
  }),
    /* more request options */
});
```

每执行 `add` 时，浏览器都会主动请求并缓存返回的 Response。

可以通过 `addAll` 批量添加缓存：

```typescript
myCache.addAll(["/subscribe", "/assets/images/profile.png"]);
```

## 读取缓存

通过 `match` 读取缓存。与 `add` 类似，参数可以是 URL 地址或完整 Request 对象，同时支持 `matchAll`：

```typescript
const res = await myCache.match("/subscribe");
```

## 更新缓存

通过 `add` 或 `put` 更新缓存。

当某个请求缓存需要更新时，你可以重新执行 `add` 操作。

同时 `put` 也可以更新缓存，你可以手动构造返回值，这样浏览器就不需要发请求了：

```typescript
const request = new Request("/subscribe");
const fetchResponse = await fetch(request);
myCache.put(request, fetchResponse);
```

## 销毁缓存

通过 `delete` 销毁缓存。

你可以销毁某个路径的缓存：

```typescript
myCache.delete("/subscribe");
```

也可以销毁某个缓存命名空间：

```typescript
caches.delete("myCache");
```

## 结合 service Worker

可以利用 `addEventListener('fetch')` 监听浏览器请求时机，并在匹配到缓存时，直接替换为返回结果，当缓存不存在时才继续发请求。

```typescript
self.addEventListener("fetch", (e) => {
    e.respondWith(
        // Check if item exists in cache
        caches.match(e.request).then((cachedResponse) => {

            // If found in cache, return cached response
            if (cachedResponse) return cachedResponse;

            // If not found, fetch over network
            return fetch(e.request);
        });
  );
});
```

# 3 精读

笔者利用 `caches` API + service worker 实现了纯浏览器端的后端渲染。

首先基于下面三个基本事实：

- 利用 service worker 可以拦截请求。
- caches 可以主动 `put` 修改缓存。
- `react-dom/server` 可以在浏览器端执行。

这三个能力组合一下，我们真的可以实现前端 SSR：

1. 打开页面时，利用 web worker 调用 `react-dom/server` 构造一个 SSR 字符串。
2. 利用 `caches.put` 添加当前页面缓存，将 `react-root` 部分塞入构造好的 SSR 字符串。
3. 下次打开页面时，优先命中缓存，仿佛是后端提供了 SSR 服务，但其实服务是由上一次浏览器提供的。

前端渲染有几个好处：

1. 不消耗服务器计算资源，如果页面有百万 UV，可能一天就能节省几十万元服务器电费。
2. 不消耗服务器存储资源，如果页面是千人千面的，后端 SSR 存储成本巨大，但分摊到个人电脑就不成问题。
3. 不需要写两套代码。虽然服务端渲染重复利用前端资源，但 DOM 环境等都是模拟出来的，且前端代码还存在内存泄露风险，许多 SSR 的前端代码必须判断前后端环境，给维护造成了巨大负担。在前端渲染下这不成问题，我们的口号是：前端代码请交给浏览器执行。

> 笔者将这套前端渲染能力封装在 [前端工程化工具 Pri](https://github.com/prijs/pri) 中，开启配置项 `useServiceWorker=true` `clientServerRender=true` 尝试。

后面有机会单独选一篇精读介绍 前端渲染，你也可以直接参考笔者 [简陋的实现](https://github.com/prijs/pri/blob/master/src/built-in-plugins/client-ssr/index.ts#L65)：由于 service worker 必须存在一个实体文件，因此脚手架会自动生成它，所以你看到的运行代码是一堆字符串。

# 4 总结

前端渲染是一个较为极端的例子，`caches` 更多用来缓存简单的静态页面，静态博文，或者不经常变动的后端接口。

留下一个思考题：你还能想到 `caches` 的其他用法吗？欢迎留言。

> 讨论地址是：[精读《Caches API》 · Issue #124 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/124)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
