Safari and HTTP Caching

This post is more than 2 years old.

Well, this was a weird one. Yesterday I was working on a project where I made multiple requests to the Random User API. It worked perfectly in Chrome, and Android, but in Safari, I noticed something odd. Even though I made multiple requests, every result was the same, not unique. Here is a simplified version of what I built.


for(var i=0;i<10;i++) {

	$.ajax({
	  url: 'http://api.randomuser.me/',
	  dataType: 'json',
	  success: function(data){
	    console.log(JSON.stringify(data.results[0].user.name));
	  }
	});

}

My code was somewhat more complex (it had Angular, Promises, even kittens thrown in), but this gives you the basic idea. Running this in Chrome I get what I'd expect, 10 random users in the console:

shot1

And ten requests in the Network panel:

shot2

So far so good. However, in Safari (well, Mobile Safari at first, but today I tested in Safari), something odd happened. Instead of ten random users, I got the same one again and again. (And before someone asks, no, it isn't the for loop or anything like that.)

shot3

Naturally I thought - ok - Safari is caching the response. But here is what threw me for a loop. I went into the Timelines panel, turned on Recording, and this is what I saw:

shot4

Looking at this, you can see Safari made one network request, which I suppose makes sense, but here is what ticks me off. Nowhere in this panel is any indication that it simply ignored my Ajax calls and used a cache result.

To be clear, I'm totally fine with Safari ignoring my request to an API that is random and deciding it knew better and should cache. Fine. What upsets me is that the dev tools do zero to let the developer know what's going on here. It should report the other 9 requests and flag them as being from a cache or some such. I guess I'll go file a bug report for this (no, I will, because that's the right thing to do), but damn was this frustrating.

For folks curious, I simply added "?safaricanbiteme="+Math.random() to the URL - just like I used to do for older IE.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Raymond Camden posted on 7/16/2015 at 7:18 PM

If anyone wants to track this bug, it is bug id 21859864.

Comment 2 (In reply to #1) by Šime Vidas posted on 7/17/2015 at 3:07 AM

It’s not in WebKit Bugzilla? https://bugs.webkit.org/sho...

Comment 3 by Šime Vidas posted on 7/17/2015 at 5:34 AM

I’ve also tested in Firefox, Edge and IE11 (demo: http://output.jsbin.com/xor... ); they match Chrome. Safari is the outlier here.

Comment 4 by meandmycode posted on 7/17/2015 at 7:51 AM

In your example, the API does not specify Cache-Control, so I believe it's non specified behavior (user agent is free to cache as it wishes). Due to the nature of that API endpoint being transient you should outreach to them to add in 'Cache-Control: no-cache', that way they are being explicit about the ability for the result to be cached (which is cannot). Cache busting works, but as a general solution it just completely writes off caching.

Comment 5 (In reply to #2) by Raymond Camden posted on 7/17/2015 at 11:32 AM

A Safari engineer had me add it to bugreport.apple.com.

Comment 6 (In reply to #4) by Raymond Camden posted on 7/17/2015 at 11:33 AM

I agree with you that the API provider should absolutely do that, but as I said in the post, this is nothing to do with the fact that it is caching, but the fact that there is no *developer* feedback that XHR requests 2-10 were skipped due to the cache. Make sense?

Comment 7 by WolffenGeist posted on 7/17/2015 at 12:45 PM

If you add the argument cache: false to your ajax call, does it still pull just the one record and make only one call?

Comment 8 (In reply to #7) by Raymond Camden posted on 7/17/2015 at 1:01 PM

To be clear, even if it did, that isn't the issue. I feel like I'm not being clear here. :) I can work around the issue w/ a random value in the URL, and maybe cache:false would work too. But that isn't the problem. The problem is that when Safari decides to not make a network request, it needs to _report_ this to the developer. Does that make sense?

Comment 9 (In reply to #8) by WolffenGeist posted on 7/17/2015 at 1:20 PM

Absolutely makes sense. If I'm remembering correctly, IE8 and IE9 do the same thing. Basically they just cache the request and don't bother making the additional calls. The cache: false argument works similar to adding a random value to the URL.

Comment 10 (In reply to #9) by Raymond Camden posted on 7/17/2015 at 1:25 PM

Nice little hack by jQuery there. I was using Angular in the 'real' code, and I know they have their own caching system, but I wonder if they have a hack like this too. FYI, I tried testing my code in IE11 and it worked perfectly. If I had an IE8 VM around I'd check it there too. (Again, not to see it cache, but to see if the dev tools back then showed it.)

Comment 11 (In reply to #7) by Šime Vidas posted on 7/17/2015 at 4:30 PM

Adding cache: false makes Safari behave like the other browsers (test: http://jsbin.com/qidaro/edi... ). I’ll have to remember that “cache: false for Safari” rule.

Comment 12 (In reply to #11) by Raymond Camden posted on 7/17/2015 at 4:47 PM

Of course - that's only jQuery. Did anyone check the Angular docs to see if $http has something similar? (Yes, I'm being lazy.)

Comment 13 (In reply to #12) by Sparticuz posted on 7/17/2015 at 7:59 PM

Yep $http.get(url,{cache:false})

Comment 14 by Stefan Dobrev posted on 7/26/2016 at 8:09 AM

We just got bitten by this bug. The strange thing is that it happens only the very first time the page is opened.

In our case we are using content negotiation and returning either JSON or HTML based on the `Accept` header sent from the client.

Has your bug report been addressed? I was unable to find it in Apple's database.

Comment 15 (In reply to #14) by Raymond Camden posted on 7/26/2016 at 2:45 PM

I don't know. I don't even know where the code is anymore.

Comment 16 (In reply to #14) by Raymond Camden posted on 7/26/2016 at 2:46 PM

Ok, I found it, but not sure how to link to it. Bug ID is 21859864.

Comment 17 (In reply to #14) by Raymond Camden posted on 7/26/2016 at 2:53 PM

Looks to still exist.

Comment 18 (In reply to #14) by Raymond Camden posted on 7/26/2016 at 2:54 PM

As an FYI, I consistently see this: For my 10 calls, calls 2-10 are duplicates. So for 10 calls, I get 2 distinct values.

Comment 19 by diegopf posted on 8/19/2016 at 10:37 AM

Thank you Raymond for share this! It has been very helpful.

Comment 20 by gwmdr posted on 2/4/2018 at 3:15 AM

Years later, Safari still returns 200 even when the file hasn't changed. And, it reads it from disk cache. Why isn't it returning 304 ?

Comment 21 (In reply to #20) by Raymond Camden posted on 2/5/2018 at 1:34 PM

You got me. I should have filed a bug report for it.

Comment 22 by OliverJAsh posted on 11/8/2018 at 2:36 PM

Some people have mentioned a filed bug with ID 21859864.

Unfortunately Apple are mean and they don't let you view other people's bug reports: https://stackoverflow.com/a...

However, I believe I have found another filed issue for this over on the WebKit bug tracker. Open since 2014 :-|

https://bugs.webkit.org/sho...

Comment 23 by Izbicki Kuba posted on 1/8/2021 at 12:35 PM

This issue is still present, on macos 14 in safari, and also on ios 14 on safari and chrome apps. Im doing 4 GET requests to a single url with a 200ms delay between each, but safari's network tab shows only a single request.