在前端缓存网络请求
大家好!
在 dev.to 上的第一篇帖子!
我叫 Harsh,是一名正在学习的全栈开发人员,正在努力学习相关知识。
今天,我将和大家一起学习如何在前端缓存网络请求。
这里讨论的代码可以在 Github 上找到,名为api-cache-example。
我正在编写一个小型应用程序,用于从我自己的后端获取一些时间戳。这是一个中型应用程序,由 React 和 Redux 组成,用 Typescript 编写。我使用 axios 作为我的 HTTP 客户端。
(顺便提一下,这段代码是用 Typescript 编写的,但可以通过类似的想法轻松扩展到 Javascript。)
我非常非常想在客户端缓存我的请求,这样就不必重复调用我的 API。
我想到了一个简单的解决方案,并开始使用拦截器来实现它。
这个想法很简单。有一个可以存储任何类型对象的缓存。如果它们的存储时间超过了缓存时间,就使它们失效。
很简单,对吧?
那么,让我们来实现它吧!
首先,我们将创建缓存。
我们将创建一个名为 的文件cacheHandler.ts
。
这里应该包含什么?
让我们逻辑地思考一下。缓存必须处理两个请求 ->
- 店铺。
- 如果有效则检索。
因此让我们创建两个函数,store()
和isValid
。
function store(key: string, value: string) {
const finalValue = `${value}${SEPARATOR}${Date.now().toString()}`;
localStorage.setItem(key, finalValue);
}
function isValid(key: string): IsValidResponse {
const value = localStorage.getItem(key);
if (value === null) {
return {
isValid: false,
};
}
const values = value.split(SEPARATOR);
const timestamp = Number(values[1]);
if (Number.isNaN(timestamp)) {
return {
isValid: false,
};
}
const date = new Date(timestamp);
if (date.toString() === 'Invalid Date') {
return {
isValid: false,
};
}
if ((Date.now() - date.getTime()) < CACHE_INTERVAL) {
return {
isValid: true,
value: values[0],
};
}
localStorage.removeItem(key);
return {
isValid: false,
};
}
如果仔细观察,isValid
会返回类型的响应IsValidResponse
,如下所示:
interface IsValidResponse {
isValid: boolean,
value?: string,
}
我们缺少常量,因此让我们添加:
const SEPARATOR = '//**//';
const CACHE_INTERVAL = 0.2 * 60 * 1000;
store()
是一个非常简单的函数,它接受一个字符串,在其后添加一个分隔符和当前日期,并将其存储在 localStorage 中。这样可以isValid()
通过按分隔符拆分来检索数据和日期。
现在我们需要检查日期是否无效或未过期,我们可以发送一个布尔值来告知调用者缓存尚未失效,我们可以使用它。
现在,我们应该使用什么作为将对象存储在 localStorage 中的键
? 我们很快会回答这个问题。您可以直接在此处
参考该文件。 现在,进入 axios 客户端。 我们首先创建一个客户端:
export const client = axios.create({ baseURL: 'http://localhost:8080/api/widget', withCredentials: true });
baseURL
可以是任何值,取决于你想发送请求的位置。
我有一个在 8080 端口的服务器,它返回一个包含今日天气信息的 JSON 对象,但实际上你可以使用任何 API。
现在我们添加拦截器:
client.interceptors.request.use((request) => requestHandler(request));
client.interceptors.response.use(
(response) => responseHandler(response),
(error) => errorHandler(error),
);
const whiteList = ['weather'];
function isURLInWhiteList(url: string) {
return whiteList.includes(url.split('/')[1]);
}
function responseHandler(response: AxiosResponse<any>): AxiosResponse<any> {
if (response.config.method === 'GET' || 'get') {
if (response.config.url && !isURLInWhiteList(response.config.url)) {
console.log('storing in cache');
cache.store(response.config.url, JSON.stringify(response.data));
}
}
return response;
}
function errorHandler(error: any) {
if (error.headers.cached === true) {
console.log('got cached data in response, serving it directly');
return Promise.resolve(error);
}
return Promise.reject(error);
}
function requestHandler(request: AxiosRequestConfig) {
if (request.method === 'GET' || 'get') {
const checkIsValidResponse = cache.isValid(request.url || '');
if (checkIsValidResponse.isValid) {
console.log('serving cached data');
request.headers.cached = true;
request.data = JSON.parse(checkIsValidResponse.value || '{}');
return Promise.reject(request);
}
}
return request;
}
呼,一大堆代码跑过去了!
首先,我们来看一下isURLInWhiteList
。这只是为了让我们可以将一些 URL 列入黑名单,使其不存储在缓存中。这可能用于身份验证路由。
现在,我们来看看responseHandler
。
第一个 if 语句用于检查是否GET
发出了请求。
if (response.config.method === 'GET' || 'get')
如果是,那么这个 url 是不是不在白名单中?
if (response.config.url && !isURLInWhiteList(response.config.url))
如果满足这些条件,只需将对象存储在缓存中,并将键作为请求的URL
。 现在我们来处理requestHandler
第一个条件。
第一个 if 语句用于检查是否GET
发出了请求。
if (response.config.method === 'GET' || 'get')
然后检查缓存是否有效
const checkIsValidResponse = cache.isValid(request.url || '');
if (checkIsValidResponse.isValid)
如果是,则意味着缓存仍然有效,我们可以直接提供缓存,而不必发送响应!
因此,在请求中添加一个标头,并命名为cached
(可以是任何名称,这是我的个人偏好),然后将其设置为 true。
request.headers.cached = true;
此处设置请求数据仅到缓存
request.data = JSON.parse(checkIsValidResponse.value || '{}');
然后,Promise.reject请求。
为什么?
这样做是因为这个请求会立即被发送errorHandler
。在这里,我们只需检查是否有cached
标头即可。如果是,则表示数据已被缓存,而不是真正的错误。否则,我们可以直接拒绝该错误。
这就是我们要做的。
function errorHandler(error: any) {
if (error.headers.cached === true) {
console.log('got cached data in response, serving it directly');
return Promise.resolve(error);
}
return Promise.reject(error);
}
如果缓存的标头存在,我们就返回一个Promise.resolve,这样 axios 就会把它当作没有发生过错误,并且我们会在.then
而不是中获取这些数据.catch
。这样get
一来,调用者就不知道后台正在发生缓存!
如果是其他错误,就直接返回一个Promise.reject,这样它的行为就和普通错误一样了!是不是很酷?
我在一个 React 应用中使用了这种设计,代码如下:
从 1604 毫秒缩减至惊人的3 毫秒。
这比非缓存版本快了535
倍。 通过更改常量CACHE_INTERVAL
,我们可以修改缓存保持验证状态的时间。你可以在我的 GitHub 账户
上查看该项目。
离开之前还有最后一个问题。我该如何将它与fetch一起使用?
嗯,有些问题应该留给读者自己去回答。否则,学习还有什么意义呢?