stale-while-revalidate 缓存策略
stale-while-revalidate
(简称SWR) 是 HTTP 的一种缓存策略,是 HTTP 的响应头 cache-control
的一个属性值。 一种由 HTTP RFC 5861(opens in a new tab) 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。
如果你有使用过 NEXTjs 或 SWR 的话,其中都有用到
stale-while-revalidate
,比如 nextjsgetStaticProps
返回的参数revalidate
就是设置这个属性。
浏览器 SWR 缓存策略主要分为两部分:
- 判断响应缓存是否过期。
- 重新校验响应数据时效性。
📨 例子
假设有一请求的响应头有如下字段👇🏻
Cache-Control: max-age=600, stale-while-revalidate=30
- 那么浏览器收到该响应后,会将结果缓存到磁盘中,请求的结果在600s内都是新鲜(stale的反义词)的,如果在600s内发起了相同请求,则直接返回磁盘缓存。
- 若 600s~630s(
max-age
+stale-while-revalidate
)内发起了相同的请求 ,虽然缓存过期了,但依旧使用该缓存结果;另外同时进行异步 revalidate,即重新验证,相当于浏览器在背后自己发一个请求,响应结果留作下次使用。 - 在 630s 秒后,发起相同请求时,则将和第一次请求一样,从服务器获取响应结果,并且浏览器并把响应结果缓存起来。
那么使用它的优点是❓❓❓ 在传统的同步更新缓存机制中,一个资源的缓存过期之后,如果想再次使用它,需要先对该缓存进行 revalidate。在 revalidate 执行期间,客户端就得等待,直到 revalidate 请求结束。 而 stale-while-revalidate
可以进行后台缓存刷新,提升加载速度。
没有十全十美的东西,自然它也不例外👇🏻 stale-while-revalidate
进行异步缓存更新,因此 revalidate 期间时返回上一次的缓存结果,这也导致会牺牲一次(通常)资源的新鲜性。
实操案例
服务端代码
javascript
const http = require('http')
const app = http.createServer()
let firstRequest = true
app.on('request', (req, res) => {
const { url } = req
const { host } = req.headers
const route = new URL(url, `http://${host}`).pathname
let response = {
stat: 0,
data: {}
}
switch (route) {
case '/api/list':
// 模拟请求后一次后,响应数据改变
if(firstRequest) {
response = {
stat: 1,
data: '第一次请求的数据'
}
firstRequest = false
} else {
response = {
stat: 1,
data: '最新的数据'
}
}
break
default:
break
}
res.setHeader("Access-Control-Allow-Origin", "*")
res.setHeader('Content-Type', 'application/json; charset=utf-8')
res.setHeader('Cache-Control', 'max-age=30, stale-while-revalidate=10')
res.end(JSON.stringify(response))
})
app.listen(8888, () => {
console.log('webserver running')
})
客户端代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>第一次请求的数据:<span id="first"></span></div>
<div>30s~40s请求的数据:<span id="second"></span></div>
<div>40s后请求的数据:<span id="third"></span></div>
<script>
async function fetchData() {
const res = await (await fetch('http://localhost:8888/api/list')).json()
return res
}
async function render() {
const firstRes = await fetchData()
document.getElementById('first').innerText = JSON.stringify(firstRes)
setTimeout(async () => {
// 31秒后自动请求
const secondRes = await fetchData()
document.getElementById('second').innerText = JSON.stringify(secondRes)
}, 31000)
setTimeout(async () => {
// 41秒后自动请求
const secondRes = await fetchData()
document.getElementById('third').innerText = JSON.stringify(thirdRes)
}, 41000)
}
render()
</script>
</body>
</html>
运行结果👇🏻
第一次请求的数据:{stat: 1,data: '第一次请求的数据'}
30s~40s请求的数据:{stat: 1,data: '第一次请求的数据'}
40s后请求的数据:{stat: 1,data: '最新的数据'}
.