Reputation: 46247
This is basically the opposite of fetch(), how do you make a non-cached request?.
Let's say we have client-side:
<div onclick="fetch('/data.php').then(r => r.text()).then(s => console.log(s));">Click me</div>
and server-side in PHP:
<?php echo "hello"; /* in reality, this can be much more data
and/or updated later with other content */ ?>
When clicking multiple times on Click me
, the request is done multiple times over the network with request response code "200".
Thus, no caching has been done by the browser / by PHP:
How to prevent this data to be transferred multiple times if it is in fact not needed here?
Potential solution: Since /data.php
can be updated on the server, the server could serve the data requests with a datetime metadata timestamp
, that I could store in a client-side JS variable timestamp
. Then before doing the next fetch
request for /data.php
, I could do a preliminary request
fetch('/get_last_modification_date.php').(r => r.json()).then(data => {
if (data['timestamp'] != timestamp)
fetch('/data.php')... // if and only if new data is here
});
Thus, we only do a fetch('/data.php')...
if the timestamp of this data is more recent than the one we already had.
This would work, but it seems we are manually doing something that could be done by the browser+PHP cache mechanism.
How to ask PHP and the browser to do all this caching for us?
Upvotes: 4
Views: 1339
Reputation:
use + Date.now()
for unique requests;
and don't use static text in experiments like this ,use dynamic content in output:
i did my own experiment(in xampp windows) base on your question (in firefox) look in console and then in server's headers section ..
index.php :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="Custa">
<title></title>
</head>
<body>
<div onclick="fetch('data.php?'+ Date.now()).then(r => r.text()).then(s => console.log(s));">Click me</div>
</body>
</html>
data.php :
<?php
echo microtime(true);
?>
Upvotes: 0
Reputation: 18220
fetch()
behaves the same as a regular HTTP request, so you can just apply the standard HTTP rules regarding caching.
For example you can use the If-Modified-Since
mechanism, that I will explain.
When a server returns the following headers for a resource:
Cache-Control: no-cache
Last-Modified: <date in RFC2616 format>
then, on subsequent requests, the browser will send an If-Modified-Since
header with the Last-Modified
date.
If the server returns a 304
(Not Modified) status, then the browser will use the cached version of the resource.
Remark: despite its name, the no-cache
directive doesn't mean that the resource can't be cached; it just means
that the browser must validate it with the server before using it.
To illustrate this, I will assume that you want to send the contents of some data.txt
file:
<?php
// Get the modification time of the resource
$dataTime = filemtime('data.txt');
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
{
$sinceTime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if($sinceTime==$dataTime)
{
// The browser already has the latest version of the resource
header('HTTP/1.1 304 Not Modified');
exit;
}
}
// Send the resource
header('Cache-Control: no-cache');
header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $dataTime));
header('Content-Type: text/plain; charset=UTF-8');
readfile('data.txt');
?>
Upvotes: 3
Reputation: 1332
I see two solutions to this; and I've implemented both already throughout my applications, so they both work.
Solution A : Specify the cache value when using the fetch API, instead of just passing the link from which you want to fetch a given resource / post specific data / whatever.
Choose from default
, no-store
, reload
, no-cache
, force-cache
or only-if-cached
according to your needs, as explained in the first link.
Combine this with your mentioned timestamp
to accordingly decide if the cache is still fresh
/ stale
etc. (as explained in the first link), and you're good to go.
Solution B : I've implemented in this in some scenarios where for example multiple kind of GET
requests are allowed to the same endpoint, while however some requests (like GET https://example.org/api/v1/users
) make subsequent ones, that are further narrowed down (like GET https://example.org/api/v1/users/employees/4
), obsolete.
In such a scenario B, I initiate a JS object, like so:
const users = {};
Suppose I then for example fetch the users via GET https://example.org/api/v1/users
, and that that requests also retrieves the employees for this API (for whatever reason) under an employees
key.
I populate users
with the retrieved data, hence also with an employees
key, holding for example 5 employees.
Then, on subsequent requests, like GET https://example.org/api/v1/users/employees/4
; I check for example in this case if the users
object already retrieved the employees
by doing stuff like:
if (!users.hasOwnProperty('employees')) { // Only fetch your data in this case }
To only make a request to the server if the data is not already present in my local js environment. You then may add a timestamp
property to your users
object according to your needs, and check in the condition above if the timestamp of subsequent requests is below that timestamp
, e.g. the request should be retrieved from the local variable, and not from the server. This kind of mimicks your cache, it's the same principle.
Of course, solution B really depends on how you've setup your server responses etc., so I'd say solution A is the standard approach.
For further infos regarding solution A, you probably also want to have a quick read through when is a HTTP Request's cache still considered fresh?, or rather just checkout the entire HTTP Cache docs quickly.
And you may also want to have a look at this question, as the first answer shows an example of how you could also explicitly set the Cache-Control
Header. In this sense; you should consider the value of the max-age
key of the Cache-Control
header you set as your timestamp
. This is how browsers generally handle caching natively.
Upvotes: 2
Reputation: 4415
A simple way is to wrap the call to fetch
Ideally this would be in a class or closure, but global variables now for brevity’s sake.
var fetched = false;
function clickMe(someURL) {
if (fetched) { return; }
fetched = true;
fetch(someURL).then() // etc.
}
// onclick="clickMe('/data.php')"
Upvotes: 1