缓存在本地提取Ajax请求:包装Fetch API
Feb 17, 2025 am 11:06 AM本文由特邀作者Peter Bengtsson撰写。SitePoint特邀文章旨在为您带来来自JavaScript社区知名作家和演讲者的精彩内容
本文演示了如何实现已提取请求的本地缓存,以便如果重复执行,则从会话存储中读取。这样做的好处是,您无需为要缓存的每个资源编写自定义代码。
如果您想在下次JavaScript聚会上炫耀一番,展示您在处理Promise、最先进的API和本地存储方面的各种技能,请继续阅读。
主要收获
- 利用Fetch API,开发人员可以创建AJAX请求的本地缓存,通过减少冗余的网络调用和加快数据检索来提高效率。
- 使用全局变量进行缓存的简单方法受会话持久性的限制;切换到会话存储允许数据在同一会话中跨页面重新加载持久存在。
- 实现
cachedFetch
封装了标准的fetch
调用,可以根据内容类型和URL自动缓存响应,从而使缓存机制通用化。 -
cachedFetch
的增强功能包括在进行网络请求之前处理来自会话存储的缓存命中,以及管理内容过期以避免使用过时数据。 - 未来的改进可能包括处理二进制数据和使用哈希URL作为缓存键,以优化Web应用程序中的存储和检索过程。
Fetch API
此时,您应该熟悉fetch。它是浏览器中一个新的原生API,用于替换旧的XMLHttpRequest API。
Can I Use fetch? https://www.php.cn/link/b751ea087892ebeca363034301f45c69网站上关于主要浏览器对fetch功能支持的数据。
在并非所有浏览器都完美实现的地方,您可以使用GitHub的fetch polyfill(如果您整天无所事事,这里有Fetch标准规范)。
简单的替代方案
假设您确切知道需要下载哪个资源,并且只想下载一次。您可以使用全局变量作为缓存,如下所示:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
这仅仅依赖于全局变量来保存缓存的数据。直接的问题是,如果您重新加载页面或导航到新页面,缓存的数据就会消失。
在我们剖析其缺点之前,让我们升级一下第一个简单的解决方案。
fetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { sessionStorage.setItem('information', JSON.stringify(info)); }); // 需要延迟以确保fetch已完成 setTimeout(() => { let info = JSON.parse(sessionStorage.getItem('information')); console.log('您的来源是 ' + info.origin); }, 3000);
第一个直接的问题是fetch是基于Promise的,这意味着我们无法确定它何时完成,因此为了确定起见,我们不应依赖于它的执行,直到它的Promise解析。
第二个问题是此解决方案非常特定于特定的URL和特定的缓存数据片段(在此示例中为关键信息)。我们想要的是一个基于URL的通用解决方案。
第一次实现 – 保持简单
让我们围绕fetch创建一个包装器,它也返回一个Promise。调用它的代码可能并不关心结果是来自网络还是来自本地缓存。
所以想象一下您曾经这样做:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
现在您想对其进行包装,以便重复的网络调用可以从本地缓存中获益。让我们简单地将其称为cachedFetch,因此代码如下所示:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { sessionStorage.setItem('information', JSON.stringify(info)); }); // 需要延迟以确保fetch已完成 setTimeout(() => { let info = JSON.parse(sessionStorage.getItem('information')); console.log('您的来源是 ' + info.origin); }, 3000);
第一次运行时,它需要通过网络解析请求并将结果存储在缓存中。第二次应该直接从本地存储中提取。
让我们从简单地包装fetch函数的代码开始:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(issues => { console.log('您的来源是 ' + info.origin); });
这可以工作,但当然没用。让我们首先实现存储提取的数据。
cachedFetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { console.log('您的来源是 ' + info.origin); });
这里有很多事情要做。
fetch返回的第一个Promise实际上会继续执行GET请求。如果CORS(跨源资源共享)有问题,.text()、.json()或.blob()方法将无法工作。
最有趣的功能是,我们必须克隆第一个Promise返回的Response对象。如果我们不这样做,我们就会过度注入自己,当Promise的最终用户尝试调用.json()(例如)时,他们会收到此错误:
const cachedFetch = (url, options) => { return fetch(url, options); };
需要注意的另一件事是对响应类型的仔细处理:我们只在状态码为200 并且内容类型为application/json或text/*时才存储响应。这是因为sessionStorage只能存储文本。
以下是如何使用它的示例:
const cachedFetch = (url, options) => { // 使用URL作为sessionStorage的缓存键 let cacheKey = url; return fetch(url, options).then(response => { // 让我们只在内容类型为JSON或非二进制内容时存储在缓存中 let ct = response.headers.get('Content-Type'); if (ct && (ct.match(/application\/json/i) || ct.match(/text\//i))) { // 有一个.json()而不是.text(),但我们将它存储在sessionStorage中作为字符串。 // 如果我们不克隆响应,它将在返回时被使用。这样我们就可以不干扰。 response.clone().text().then(content => { sessionStorage.setItem(cacheKey, content); }); } return response; }); };
到目前为止,这个解决方案的巧妙之处在于它可以工作,而且不会干扰JSON和HTML请求。当它是图像时,它不会尝试将其存储在sessionStorage中。
第二次实现 – 实际返回缓存命中
因此,我们的第一次实现只是负责存储请求的响应。但是,如果您第二次调用cachedFetch,它仍然不会尝试从sessionStorage检索任何内容。我们需要做的首先是返回一个Promise,并且Promise需要解析一个Response对象。
让我们从一个非常基本的实现开始:
<code>TypeError: Body has already been consumed.</code>
它可以工作!
要查看它的实际效果,请打开此代码的CodePen,然后在开发者工具中打开浏览器的“网络”选项卡。按几次“运行”按钮(CodePen的右上角),您应该会看到只有图像正在重复通过网络请求。
此解决方案的一个巧妙之处在于缺乏“回调意大利面”。由于sessionStorage.getItem调用是同步的(即阻塞的),我们不必在Promise或回调中处理“它是否在本地存储中?”。并且只有在有内容的情况下,我们才会返回缓存的结果。如果没有,if语句只会继续执行常规代码。
第三次实现 – 过期时间呢?
到目前为止,我们一直在使用sessionStorage,它就像localStorage一样,只是sessionStorage在您启动新选项卡时会被清除。这意味着我们正在利用一种“自然方式”来避免缓存时间过长。如果我们改用localStorage并缓存某些内容,即使远程内容已更改,它也会永远卡在那里,这很糟糕。
更好的解决方案是让用户控制。(在这种情况下,用户是使用我们的cachedFetch函数的Web开发人员)。就像服务器端的Memcached或Redis存储一样,您可以设置一个生存期,指定应缓存多长时间。
例如,在Python(使用Flask)中:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
现在,sessionStorage和localStorage都没有内置此功能,因此我们必须手动实现它。我们将通过始终记录存储时间的时间戳来做到这一点,并使用它来比较可能的缓存命中。
但在我们这样做之前,它会是什么样子?比如这样:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { sessionStorage.setItem('information', JSON.stringify(info)); }); // 需要延迟以确保fetch已完成 setTimeout(() => { let info = JSON.parse(sessionStorage.getItem('information')); console.log('您的来源是 ' + info.origin); }, 3000);
我们将添加的关键新内容是,每次保存响应数据时,我们也会记录何时存储它。但请注意,现在我们也可以切换到localStorage的更可靠存储,而不是sessionStorage。我们的自定义过期代码将确保我们不会在持久性localStorage中获得非常陈旧的缓存命中。
所以这是我们最终的工作解决方案:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(issues => { console.log('您的来源是 ' + info.origin); });
未来的实现 – 更好、更花哨、更酷
我们不仅避免了过度访问这些Web API,最好的部分是localStorage比依赖网络快得多。请参阅这篇博文以了解localStorage与XHR的比较:localForage vs. XHR。它测量其他内容,但基本上得出结论,localStorage非常快,磁盘缓存预热很少见。
那么我们如何进一步改进我们的解决方案呢?
处理二进制响应
我们这里的实现不会缓存非文本内容(如图像),但没有理由不能缓存。我们需要更多代码。特别是,我们可能想要存储更多关于Blob的信息。每个响应基本上都是一个Blob。对于文本和JSON,它只是一个字符串数组。类型和大小并不重要,因为您可以从字符串本身推断出来。对于二进制内容,blob必须转换为ArrayBuffer。
对于好奇的人,要查看支持图像的实现扩展,请查看此CodePen:[https://www.php.cn/link/946af3555203afdb63e571b873e419f6]。
使用哈希缓存键
另一个潜在的改进是通过对每个URL(我们用作键)进行哈希处理来用空间换取速度,使其变得更小。在上面的示例中,我们只使用了一些非常小巧简洁的URL(例如https://httpbin.org/get),但是如果您有非常长的URL,有很多查询字符串内容,并且有很多这样的URL,那么它们加起来就会非常多。
解决这个问题的方法是使用这种巧妙的算法,它被认为是安全且快速的:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
如果您喜欢这个,请查看此CodePen:[https://www.php.cn/link/946af3555203afdb63e571b873e419f6]。如果您在Web控制台中检查存储,您会看到类似于557027443的键。
结论
您现在有一个可以添加到Web应用程序中的工作解决方案,在该解决方案中,您可能正在使用Web API,并且您知道响应可以很好地为您的用户缓存。
最后一件事可能是此原型的自然扩展,即将其超越文章,进入一个真实的、具体的项目,带有测试和自述文件,并在npm上发布它——但这留待以后再说!
关于缓存已提取AJAX请求的常见问题解答 (FAQ)
缓存已提取AJAX请求的重要性是什么?
缓存已提取的AJAX请求对于提高Web应用程序的性能至关重要。它允许浏览器存储服务器响应的副本,以便它不必再次发出相同的请求。这减少了服务器的负载,并加快了网页的加载时间,从而提供了更好的用户体验。
Fetch API如何与缓存一起工作?
Fetch API提供了一种强大且灵活的方法来发出HTTP请求。它包含一个内置的缓存机制,允许您指定请求应如何与缓存交互。您可以将缓存模式设置为“default”、“no-store”、“reload”、“no-cache”、“force-cache”或“only-if-cached”,每种模式都提供不同级别的缓存控制。
Fetch API中有哪些不同的缓存模式,它们是什么意思?
Fetch API提供了几种缓存模式。“default”遵循标准的HTTP缓存规则。“no-store”完全绕过缓存。“reload”忽略任何缓存数据并发送新的请求。“no-cache”在使用缓存版本之前使用服务器验证数据。“force-cache”无论其新鲜度如何都使用缓存数据。“only-if-cached”仅在缓存数据可用时才使用它,否则失败。
如何在AJAX请求中实现缓存?
您可以通过在AJAX设置中设置cache属性来在AJAX请求中实现缓存。如果设置为true,它将允许浏览器缓存响应。或者,您可以使用Fetch API的缓存选项来更好地控制缓存的行为。
如何防止AJAX请求中的缓存?
要防止AJAX请求中的缓存,您可以将AJAX设置中的cache属性设置为false。这将强制浏览器不将其响应存储在其缓存中。或者,您可以使用Fetch API的“no-store”缓存选项来完全绕过缓存。
AJAX和Fetch API中的缓存有什么区别?
虽然AJAX和Fetch API都提供了缓存机制,但Fetch API提供了更大的灵活性和控制性。AJAX的cache属性是一个简单的布尔值,它允许或不允许缓存。另一方面,Fetch API的缓存选项允许您指定请求应如何与缓存交互,从而为您提供更细粒度的控制。
缓存如何影响我的Web应用程序的性能?
缓存可以显著提高Web应用程序的性能。通过存储服务器响应的副本,浏览器不必再次发出相同的请求。这减少了服务器的负载,并加快了网页的加载时间。但是,必须正确管理缓存,以确保您的用户看到最新的内容。
我可以控制单个AJAX请求的缓存行为吗?
是的,您可以通过为每个请求在AJAX设置中设置cache属性来控制单个AJAX请求的缓存行为。这允许您指定浏览器是否应该缓存响应。
如何清除AJAX请求的缓存?
清除AJAX请求的缓存可以通过在AJAX设置中将cache属性设置为false来完成。这将强制浏览器不将其响应存储在其缓存中。或者,您可以使用Fetch API的“reload”缓存选项来忽略任何缓存数据并发送新的请求。
缓存AJAX请求的一些最佳实践是什么?
缓存AJAX请求的一些最佳实践包括:了解不同的缓存模式以及何时使用它们,正确管理缓存以确保用户看到最新的内容,以及使用Fetch API的缓存选项来更好地控制缓存。在决定缓存策略时,还必须考虑数据的性质和用户体验。
以上是缓存在本地提取Ajax请求:包装Fetch API的详细内容。更多信息请关注PHP中文网其他相关文章!

热门文章

热门工具标签

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)