花了好几个小时,终于懂了什么叫SWR

13,086 阅读3分钟

最近看到一个概念叫swr (stale-while-revalidate),但是我看了好久的文档都没有搞懂,直到自己实操才终于理解。接下来,就分享一下什么是SWR

具体概念

首先,官方介绍在这里:www.rfc-editor.org/rfc/rfc5861…

The stale-while-revalidate HTTP Cache-Control extension allows a cache to immediately return a stale response while it revalidates it in the background, thereby hiding latency (both in the network and on the server) from clients.

我按自己的理解翻译一下:

stale-while-revalidate是HTTP的响应头cache-control的一个属性值,它允许立马返回一个已经过时(stale)的响应。与此同时浏览器又在背后默默重新发起一次请求,响应的结果被存储起来下次使用。因此它很好的隐藏了服务器或者网络的响应延时。

为什么这里说允许返回一个stale的响应呢?如何判断响应是stale的呢,这是因为stale-while-revalidate是和max-age 一起使用的,如果时间超过了max-age,则是作为stale。

Cache-Control: max-age=600, stale-while-revalidate=30
  • 表示请求的结果在600s内都是新鲜(stale的反义词)的,如果在600s内发起了相同请求,则直接返回磁盘缓存。

  • 如果在600s~630s内发起了相同的请求,则响应虽然已经过时(stale)了,但是浏览器会直接把之前的缓存结果返回,与此同时浏览器又在背后自己发一个请求,响应结果留作下次使用。

  • 如果超过630s后,发起了相同请求,则这就是一个普通请求,就和第一次请求一样,从服务器获取响应结果,并且浏览器并把响应结果缓存起来。

image.png

实际操作

接下来,用express作为后端示例一下:

import express from "express";
const app = express();
app.set("etag", false);
app.use(express.static("public"));
let counts = 1;
app.get("/api/counts", (req,res) => {
  console.log("request counts:", counts);
  // 设置Cache-Control
  res.append("Cache-Control", "max-age=5, stale-while-revalidate=10");
  // 手动模拟服务器响应或者网络很慢
  setTimeout(() => {
    res.json({  counts });
    console.log("response counts", counts);
    counts = counts + 1;
  }, 2000);
});

app.listen(3000, () => {
  console.log("running at http://localhost:3000");
});

然后public文件夹放了如下index.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <style>
    details {
      margin-top: 1em;
    }

    script.visible {
      border: 1px solid #009688;
      display: block;
      font-family: monospace;
      margin-top: 1em;
      padding: 0.2em;
      overflow-x: auto;
      white-space: pre;
    }

    #counts {
      background-color: yellow;
    }
  </style>
</head>

<body>
  <h1>Stale-while-revalidate Demo</h1>
  <p id="counts"></p>
  <button id="refresh">Refresh Request Count</button>
  <details>
    <summary>View JavaScript</summary>
    <script class="visible">
      const countsEl = document.getElementById("counts");
      const refreshEl = document.getElementById("refresh");
      async function updateCounts() {
        const response = await fetch("/api/counts");
        const responseBody = await response.json();
        countsEl.textContent = responseBody.counts;
      }
      updateCounts();
      refreshEl.addEventListener("click", updateCounts);
    </script>
  </details>
</body>

</html>

具体就是点击按钮发起一次请求,然后获取数据。

image.png

这里我们设置的是max-age=5, stale-while-revalidate=10

所以如果我们在5s发起第二次请求,就浏览器就会让我们直接用缓存数据,服务器都不会收到这次请求,可以看到左侧服务器打印的还是1!

image.png

如果在5s~15s内发起第二次请求:该解释的我都放在下图了。

image.png

总结

stale-while-revalidate, 我觉得还是很难理解的,需要自己写个简单的api试一试。

  1. 明白什么时候stale-while-revalidate生效:在max-age ~ max-age+stale-while-revalidate之间生效。
    # 在600s~630s之间,浏览器会进行stale-while-revalidate
    Cache-Control: max-age=600, stale-while-revalidate=30
    
  2. 什么是stale-while-revalidate:浏览器返回一个已经过时的响应缓存,与此同时浏览器又在背后向服务器发起一次请求,并缓存响应结果。

参考文档:

  1. www.rfc-editor.org/rfc/rfc5861…
  2. web.dev/stale-while…