在前端缓存网络请求

2025-06-10

在前端缓存网络请求

大家好!

在 dev.to 上的第一篇帖子!

我叫 Harsh,是一名正在学习的全栈开发人员,正在努力学习相关知识。

今天,我将和大家一起学习如何在前端缓存网络请求。

这里讨论的代码可以在 Github 上找到,名为api-cache-example

我正在编写一个小型应用程序,用于从我自己的后端获取一些时间戳。这是一个中型应用程序,由 React 和 Redux 组成,用 Typescript 编写。我使用 axios 作为我的 HTTP 客户端。

(顺便提一下,这段代码是用 Typescript 编写的,但可以通过类似的想法轻松扩展到 Javascript。

我非常非常想在客户端缓存我的请求,这样就不必重复调用我的 API。
我想到了一个简单的解决方案,并开始使用拦截器来实现它。
这个想法很简单。有一个可以存储任何类型对象的缓存。如果它们的存储时间超过了缓存时间,就使它们失效。

很简单,对吧?
那么,让我们来实现它吧!

首先,我们将创建缓存。
我们将创建一个名为 的文件cacheHandler.ts
这里应该包含什么?
让我们逻辑地思考一下。缓存必须处理两个请求 ->

  1. 店铺。
  2. 如果有效则检索。

因此让我们创建两个函数,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一起使用?
嗯,有些问题应该留给读者自己去回答。否则,学习还有什么意义呢?

鏂囩珷鏉ユ簮锛�https://dev.to/nosyminotaur/caching-network-requests-on-the-frontend-dmh
PREV
Kickass VS Code 扩展让你的生活更轻松
NEXT
React 与 Svelte 对比