Home >Web Front-end >JS Tutorial >Cache Fetched AJAX Requests Locally: Wrapping the Fetch API
This article was written by invited author Peter Bengtsson. SitePoint's special guest post is designed to bring you great content from well-known writers and speakers in the JavaScript community
This article demonstrates how to implement a local cache of fetched requests so that if executed repeatedly, it will be read from the session store. The benefit of this is that you don't need to write custom code for every resource you want to cache.
If you want to show off your next JavaScript party and show off your various skills in handling Promise, state-of-the-art APIs, and local storage, read on.
cachedFetch
encapsulates the standard fetch
calls, which can automatically cache responses based on content type and URL, thus making the cache mechanism universal. cachedFetch
include handling cache hits from session storage before making a network request, and managing content expired to avoid using outdated data. At this point, you should be familiar with fetch. It is a new native API in the browser for replacing the old XMLHttpRequest API.
Can I Use fetch? https://www.php.cn/link/b751ea087892ebeca363034301f45c69 data on the website about the main browser's support for fetch function.
Where not all browsers are perfectly implemented, you can use GitHub's fetch polyfill (there are Fetch standard specifications here if you're doing nothing all day).
Suppose you know exactly which resource you need to download and you only want to download it once. You can use global variables as cache as follows:
<code class="language-javascript">let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);</code>
This only depends on global variables to hold cached data. The direct problem is that if you reload the page or navigate to a new page, the cached data will disappear.
Before we dissect its shortcomings, let's upgrade the first simple solution.
<code class="language-javascript">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);</code>
The first direct problem is that fetch is based on Promise, which means we can't determine when it will be done, so for the sake of certainty we should not rely on its execution until its Promise parses.
The second problem is that this solution is very specific to specific URLs and specific cached data snippets (key information in this example). What we want is a universal URL-based solution.
Let's create a wrapper around fetch which also returns a promise. The code that calls it may not care whether the result is from the network or from the local cache.
So imagine you used to do this:
<code class="language-javascript">let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);</code>Now you want to wrap it so that duplicate network calls can benefit from local cache. Let's simply call it cachedFetch, so the code looks like this:
<code class="language-javascript">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);</code>The first time it runs, it needs to parse the request over the network and store the result in the cache. The second time should be extracted directly from local storage.
Let's start with the code that simply wraps the fetch function:
<code class="language-javascript">fetch('https://httpbin.org/get') .then(r => r.json()) .then(issues => { console.log('您的来源是 ' + info.origin); });</code>This works, but of course it doesn't work. Let's first implement the storage of extracted data.
<code class="language-javascript">cachedFetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { console.log('您的来源是 ' + info.origin); });</code>There are a lot to do here.
The first promise returned by fetch will actually continue to perform the GET request. If there is a problem with CORS (cross-origin resource sharing), the .text(), .json(), or .blob() methods will not work.
The most interesting feature is that we have to
Clone the Response object returned by the first Promise. If we don't, we overinject ourselves, and when the end users of Promise try to call .json() (for example), they get this error:Another thing to note is the careful handling of the response type: we only store the response when the status code is 200 and the content type is application/json or text/*. This is because sessionStorage can only store text.
<code class="language-javascript">const cachedFetch = (url, options) => { return fetch(url, options); };</code>The following is an example of how to use it:
The clever thing about this solution so far is that it works without interfering with JSON
andHTML requests. When it is an image, it does not try to store it in sessionStorage.
<code class="language-javascript">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; }); };</code>Second implementation - Actual return cache hit
Therefore, our first implementation is only responsible for storing the response of the request. However, if you call cachedFetch the second time, it still won't try to retrieve anything from sessionStorage. What we need to do is to return a Promise, and the Promise needs to parse a Response object.
CodePen example
It works!
<code>TypeError: Body has already been consumed.</code>
To see how it actually works, open the CodePen of this code and then open the "Network" tab of the browser in the developer tools. Press the Run button a few times (the upper right corner of CodePen) and you should see that only the images are requesting repeatedly over the network.
One of the cleverness of this solution is the lack of "callback pasta". Since the sessionStorage.getItem call is synchronous (i.e. blocking), we don't have to deal with "Is it in local storage?" in the Promise or callback. And we will return cached results only if there is content. If not, the if statement will only continue to execute the regular code.
So far we have been using sessionStorage, it's like localStorage, just that sessionStorage is cleared when you start a new tab. This means we are leveraging a "natural way" to avoid cache time too long. If we use localStorage instead and cache something, it will always get stuck there even if the remote content has changed, which is bad.
A better solution is to let usercontrol. (In this case, the user is the web developer who uses our cachedFetch function). Just like Memcached or Redis storage on the server side, you can set a lifetime that specifies how long it should be cached.
For example, in Python (using Flask):
<code class="language-javascript">let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);</code>
Now, neither sessionStorage nor localStorage has this feature built in, so we have to implement it manually. We will do this by always logging the timestamp of the stored time and using it to compare possible cache hits.
But what will it look like before we do this? For example:
<code class="language-javascript">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);</code>
The key new thing we will add is that every time we save the response data, we will also record when when store it. But please note that now we can also switch to more reliable storage of localStorage instead of sessionStorage. Our custom expired code will ensure we don't get very stale cache hits in the persistent localStorage. So this is our ultimate working solution:
<code class="language-javascript">fetch('https://httpbin.org/get') .then(r => r.json()) .then(issues => { console.log('您的来源是 ' + info.origin); });</code>CodePen example
The realization of the future – better, more fancy, cooler
. It measures other things, but basically concludes that localStorage is very fast and disk cache warm-up is rare. So how do we further improve our solutions?
Our implementation here does not cache non-text content (such as images), but there is no reason not to cache. We need more code. In particular, we may want to store more information about the Blob. Each response is basically a blob. For text and JSON, it's just an array of strings. Type and size do not matter, as you can infer from the string itself. For binary content, the blob must be converted to an ArrayBuffer.
For curious people, to view the implementation extensions that support images, please check this CodePen: [https://www.php.cn/link/946af3555203afdb63e571b873e419f6].
Another potential improvement is to trade space for speed by hashing each URL (we use as keys) to make it smaller. In the example above, we're only using some very small and concise URLs (e.g. httpbin.org/get), but if you have very long URLs, there are a lot of query string content, and there are a lot of these URLs, then they will add up to a lot.
The solution to this problem is to use this clever algorithm, which is considered safe and fast:
<code class="language-javascript">let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);</code>
If you like this, check out this CodePen: [https://www.php.cn/link/946af3555203afdb63e571b873e419f6]. If you check the storage in the web console, you will see a key similar to 557027443.
You now have a working solution that you can add to your web application, where you are probably using the web API and you know that responses can be cached well for your users.
The last thing might be a natural extension of this prototype, that is, to move it beyond the article, into a real, specific project with tests and readmes and publish it on npm - but that's left to be said later!
Caching fetched AJAX requests is critical to improving the performance of your web application. It allows the browser to store a copy of the server response so that it does not have to make the same request again. This reduces the load on the server and speeds up the loading time of the web page, thus providing a better user experience.
Fetch API provides a powerful and flexible way to make HTTP requests. It includes a built-in caching mechanism that allows you to specify how requests should interact with the cache. You can set the cache mode to "default", "no-store", "reload", "no-cache", "force-cache", or "only-if-cached", each with different levels of cache control.
The Fetch API provides several cache modes. "default" follows standard HTTP caching rules. "no-store" bypasses the cache completely. "reload" ignores any cached data and sends new requests. "no-cache" uses the server to verify data before using the cached version. "force-cache" uses cached data regardless of its freshness. "only-if-cached" only uses it when the cache data is available, otherwise it fails.
You can implement cache in AJAX requests by setting the cache attribute in AJAX settings. If set to true, it will allow the browser to cache the response. Alternatively, you can use the cache options of the Fetch API to better control the behavior of the cache.
To prevent caching in AJAX requests, you can set the cache property in AJAX settings to false. This will force the browser not to store its response in its cache. Alternatively, you can use the "no-store" caching option of the Fetch API to bypass cache entirely.
While both AJAX and Fetch APIs provide caching mechanisms, the Fetch API provides greater flexibility and control. AJAX's cache property is a simple boolean value that allows or does not allow cache. On the other hand, the cache option of the Fetch API allows you to specify how requests should interact with the cache, giving you more granular control.
Cache can significantly improve the performance of web applications. By storing a copy of the server response, the browser does not have to make the same request again. This reduces the load on the server and speeds up the loading time of the web page. However, the cache must be managed correctly to ensure that your users see the latest content.
Yes, you can control the cache behavior of a single AJAX request by setting the cache attribute in the AJAX settings for each request. This allows you to specify whether the browser should cache the response.
Clearing the cache requested by AJAX can be done by setting the cache property to false in the AJAX settings. This will force the browser not to store its response in its cache. Alternatively, you can use the "reload" cache option of the Fetch API to ignore any cached data and send new requests.
Some best practices for caching AJAX requests include: understanding different cache modes and when to use them, managing cache correctly to ensure users see the latest content, and using the cache options of the Fetch API for better control over caches. When deciding on a caching strategy, the nature of the data and user experience must also be considered.
The above is the detailed content of Cache Fetched AJAX Requests Locally: Wrapping the Fetch API. For more information, please follow other related articles on the PHP Chinese website!