13

I noticed something odd regarding ajax and image loading. Suppose you have an image on the page, and ajax requests the same image - one would guess that ajax requests would hit the browser cache, or it should at least only make one request, the resulting image going to the page and the script that wants to read/process the image.

Surprisingly, I found that even when the javascript waits for the entire page to load, the image request still makes a new request! Is this a known bug in Firefox and Chrome, or something bad jQuery ajax is doing?

Here you can see the problem, open Fiddler or Wireshark and set it to record before you click "run":

<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<div id="something" style="background-image:url(http://jsfiddle.net/img/logo-white.png);">Hello</div>
<script>

jQuery(function($) {
    $(window).load(function() {
        $.get('http://jsfiddle.net/img/logo-white.png');
    })
});

</script>

Note that in Firefox it makes two requests, both resulting in 200-OK, and sending the entire image back to the browser twice. In Chromium, it at least correctly gets a 304 on second request instead of downloading the entire contents twice.

Oddly enough, IE11 downloads the entire image twice, while it seems IE9 aggressively caches it and downloads it once.

Ideally I would hope the ajax wouldn't make a second request at all, since it is requesting exactly the same url. Is there a reason css and ajax in this case usually have different caches, as though the browser is using different cache storage for css vs ajax requests?

sjagr
  • 15,983
  • 5
  • 40
  • 67
NoBugs
  • 9,310
  • 13
  • 80
  • 146
  • 15
    It also hilariously requests two copies of jQuery because you specified one version in the fiddle options and link to another within the code ;) – BoltClock Sep 13 '14 at 04:16
  • Could it be that your Ajax likely does not,load the image from image cache because it is not loaded into an image object? – mplungjan Sep 13 '14 at 04:17
  • @mplungjan It could be, I noticed a `$('div').load()` doesn't fire, though a `$('img').load()` does fire an event. Do you know of a workaround for this? Are background images completely separated from Javascript-accessible objects? – NoBugs Sep 13 '14 at 04:20
  • @BoltClock Haha, I just noticed that, it was copied and adjusted from a testing file - that isn't causing the problem though. – NoBugs Sep 13 '14 at 04:21
  • 8
    You know what they say... there is no such thing as too much jQuery. – BoltClock Sep 13 '14 at 04:22
  • http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/ – Control Freak Sep 13 '14 at 05:20
  • @ZeeTee That's interesting but not what I was looking for, I want JS-accessible image *and* background-image. – NoBugs Sep 13 '14 at 05:40
  • Long as the image is loaded to browser, not sure it matters where you use it. Unless I'm wrong. – Control Freak Sep 13 '14 at 05:50
  • Yes, it works, but as you can see in the example it will always make 2 requests and download it twice. – NoBugs Sep 13 '14 at 07:04
  • Are you doing a cors request? Cors requests are first sending an OPTIONS request and after that the request you actually send. – Andreas Furster Sep 16 '14 at 13:49
  • Opera reports to load the XHR from cache. The image is served with `Cache-Control: max-age=1814400` – Bergi Sep 16 '14 at 14:49
  • can you tell me what you are trying to to here? Didn't get the final output – Kshitiz Sep 18 '14 at 07:58
  • actually the 2nd connection always shows as cancelled in my chrome... – dandavis Sep 19 '14 at 21:29

9 Answers9

7

I use the newest Google Chrome and it makes one request. But in your JSFIDDLE example you are loading jQuery twice. First with CSS over style attribute and second in your code over script tag. Improved: JSFIDDLE

<div id="something" style="background-image:url('http://jsfiddle.net/img/logo-white.png');">Hello</div>

<script>
    jQuery(window).load(function() {
        jQuery.get('http://jsfiddle.net/img/logo-white.png');
    });

    // or

    jQuery(function($) {
        jQuery.get('http://jsfiddle.net/img/logo-white.png');
    });
</script>

jQuery(function($) {...} is called when DOM is ready and jQuery(window).load(...); if DOM is ready and every image and other resources are loaded. To put both together nested makes no sense, see also here: window.onload vs $(document).ready()

Sure, the image is loaded two times in Network tab of the web inspector. First through your CSS and second through your JavaScript. The second request is probably cached.

UPDATE: But every request if cached or not is shown in this tab. See following example: http://jsfiddle.net/95mnf9rm/4/ There are 5 request with cached AJAX calls and 5 without caching. And 10 request are shown in 'Network' tab. When you use your image twice in CSS then it's only requested once. But if you explicitly make a AJAX call then the browser makes an AJAX call. As you want. And then maybe it's cached or not, but it's explicitly requested, isn't it?

algorhythm
  • 8,530
  • 3
  • 35
  • 47
  • 4
    "Sure, the image is loaded to times in 'Network' tab of the web inspector. First through your CSS and second through your JavaScript." The asker is aware of this, it's what prompted the question to begin with. The question is why, because there is no immediately obvious reason a browser should load the same resource twice just because it was requested from two different contexts. If you say Google Chrome makes one request, can you explain the discrepancy between that and what you see in the Web Inspector? – BoltClock Sep 16 '14 at 14:05
  • 1
    Ah, now I undestand it exactly: You mean, why it is not cached or something like that. – algorhythm Sep 16 '14 at 14:16
  • 1
    They are cached, but the request also is shown in 'Network' tab. In following JSFIDDLE there are 10 AJAX request, first 5 with caching `true` and the others with no caching. The first 5 request are very fast and the others slow. But 10 request are in 'Network' tab. http://jsfiddle.net/95mnf9rm/4/ – algorhythm Sep 16 '14 at 14:23
  • Yes I know the difference between ready and onload, the point in the fiddle was that if you run that (even after "load"), in Wireshark or similar you can see it always downloads images *twice*! No caching something that should be loaded. – NoBugs Sep 17 '14 at 12:48
  • Yes, I see the same with Google Chrome's 'Network' tab, but my opinion on this issue was that an explicit made `get` request implies a new request to the server. There is no reason to cache it, because you told the server to deliver the image again, isn't it? Maybe the image meanwhile could be updated and you don't want it to be cached. If you `get` it you got it. – algorhythm Sep 17 '14 at 13:53
  • Thanks! I still see two requests for the image and 200 response with the entire image, twice, in Firefox 32. (No 304 not modified as in Chromium 37) Any ideas why? Seems you may need to clear all cache, then use Wireshark/fiddler to see what's really going on with requests. – NoBugs Sep 22 '14 at 05:34
  • Yeah, that is what I try to explain. It it correct to have two requests. First for CSS and second for your explicit done Ajax call. When you make a `get` the the browser connects the server and gets the file without caching or anything like that. – algorhythm Sep 23 '14 at 06:43
  • @algorhythm Ajax GET requests are cacheable. Here is an example: http://www.httpwatch.com/demos/ajax_caching/ – PSanetra Sep 23 '14 at 12:03
  • @LivingSoftware I've never said the opposite, see my some days old update in the answer... – algorhythm Sep 23 '14 at 12:13
  • You actually said "When you make a get the the browser connects the server and gets the file without caching or anything like that.". When you call the jQuery.get function the browser is just sending an Ajax GET request and there is no reason why the browser shouldn't use caching. – PSanetra Sep 23 '14 at 12:18
  • Maybe it's a bit inarticulated. But even if the file would be cached, you may need a request to the server to look if the Last-Modified header is changed, isn't it? A cache must have the ability to check if there are changes to the file. Otherwise you will cache it although the requested url has changed. – algorhythm Sep 23 '14 at 12:25
  • No, these requests are called "conditional requests". [This article](http://blogs.msdn.com/b/ieinternals/archive/2010/07/08/technical-information-about-conditional-http-requests-and-the-refresh-button.aspx) describes the reasons why IE might send a conditional request. The most important reason is "The cached item is no longer fresh according to Cache-Control or Expires". So the browser won't send a conditional request until the Cache-Control max-age or the Expires Header are expired. The server tells the browser by these headers that the browser doesn't need to send another request. – PSanetra Sep 23 '14 at 12:42
4

This "problem" could a be a CORS pre-flight test.

I had noticed this in my applications awhile back, that the call to retrieve information from a single page application made the call twice. This only happens when you're accessing URLs on a different domain. In my case we have APIs we've built and use on a different server (a different domain) than that of the applications we build. I noticed that when I use a GET or POST in my application to these RESTFUL APIs the call appears to be made twice.

What is happening is something called pre-flight (https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS), an initial request is made to the server to see if the ensuing call is allowed.

Excerpt from MDN:

Unlike simple requests, "preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data. In particular, a request is preflighted if:

  1. It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.
  2. It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
m.e.conroy
  • 3,508
  • 25
  • 27
2

Your fiddle tries to load a resource from another domain via ajax: cross domain request

I think I created a better example. Here is the code:

<img src="smiley.png" alt="smiley" />
<div id="respText"></div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(window).load(function(){        
    $.get("smiley.png", function(){
      $("#respText").text("ajax request succeeded");
    });
  });
</script>

You can test the page here.

According to Firebug and the chrome network panel the image is returned with the status code 200 and the image for the ajax request is coming from the cache:

Firefox: Firebug

Chrome: chrome network panel

So I cannot find any unexpected behavior.

PSanetra
  • 459
  • 4
  • 11
1

Cache control on Ajax requests have always been a blurred and buggy subject (example). The problem gets even worse with cross-domain references.

The fiddle link you provided is from jsfiddle.net which is an alias for fiddle.jshell.net. Every code runs inside the fiddle.jshell.net domain, but your code is referencing an image from the alias and browsers will consider it a cross-domain access.

To fix it, you could change both urls to http://fiddle.jshell.net/img/logo-white.png or just /img/logo-white.png.

  • CORS is not the problem, as you can see in algorhythm's answer and fiddle, it correctly caches (304) in Chromium but not Firefox (it sends back the whole image twice). – NoBugs Sep 22 '14 at 05:40
  • @NoBugs I meant that CORS just add a temper to the problem, and this temper is the source of your original problem. Firefox is treating caches totally different for `http://jsfiddle.net/img/logo-white.png` and for `/img/logo-white.png`, although the response headers are exactly the same (coming from the same server). However, different servers (like imgur.com caching proxies) will return different sort of cache headers and will need a different analysis, deviating from your original problem. – Sérgio Castelani Sep 22 '14 at 15:53
1

The helpful folks at Mozilla gave some details as to why this happens. Apparently Firefox assumes an "anonymous" request could be different than normal, and for this reason it makes a second request and doesn't consider the cached value with different headers to be the same request.

https://bugzilla.mozilla.org/show_bug.cgi?id=1075297

NoBugs
  • 9,310
  • 13
  • 80
  • 146
0

This may be a shot in the dark, but here's what I think is happening.

According to, http://api.jquery.com/jQuery.get/

dataType
  Type: String
  The type of data expected from the server. 
  Default: Intelligent Guess (xml, json, script, or html).

Gives you 4 possible return types. There is no datatype of image/gif being returned. Thus, the browser doesn't test it's cache for the src document as it is being delivered a a different mime type.

BReal14
  • 1,603
  • 1
  • 12
  • 35
  • But the `jQuery.ajax()` method has a option called `contentType`. There you probably can use `image/gif`. But I think it has not the expected result. – algorhythm Sep 16 '14 at 20:13
  • True, however in the example code, it is not being set, and `text/html` doesn't match `image/gif`. – BReal14 Sep 17 '14 at 14:20
  • Look in the jQuery.ajax help (which contains a lot of documentation not in .get or .post for some reason) and it is more clear - it affects jQuery's weird processing of response (auto-deserializing, auto-running-script if response looks like javascript etc.) - it shouldn't affect how it's actually fetched. http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings – NoBugs Sep 22 '14 at 05:21
  • Agreed, but those options are not being set in the example code. No call to `.overrideMimeType()` etc... I see nothing that says a default request will auto-mime itself to what it actually is. Most of the help docs focus on text entities. There is limited, if any, documentation on binary objects. I'm here to learn too and am certainly open to all suggestions, but I still believe that the ajax method is simply too dumb to realize what it has as content and doesn't invoke the browser to check it's cache because it doesn't meet the right criteria. – BReal14 Sep 22 '14 at 16:39
0

The server decides what can be cached and for how long. However, it again depends on the browser, whether or not to follow it. Most web browsers like Chrome, Firefox, Safari, Opera and IE follow it, though.

The point that I want to make here, is that your web sever might be configured to not allow your browser to cache the content, thus, when you request the image through CSS and JS, the browser follows your server's orders and doesn't cache it and thus it requests the image twice...

Anshu Dwibhashi
  • 4,617
  • 3
  • 28
  • 59
  • But with `jQuery.ajax({url: '.../img/logo-white.png', cache: true})` it will be cached. – algorhythm Sep 16 '14 at 20:17
  • 1
    @algorhythm Yes, but css requests it before jQuery so you're caching the second response from the second request... – Anshu Dwibhashi Sep 17 '14 at 06:25
  • Yes, but that's not your point. You tried to explain, that the server maybe is configured to prevent caching. And that could be refute with my line of code, isn't it? – algorhythm Sep 17 '14 at 07:29
  • 2
    Even if the server is configured to prevent caching, it completely depends on the browser whether or not to obey that... – Anshu Dwibhashi Sep 17 '14 at 10:50
  • 1
    @algorhythm,AnshumanDwibhashi: `cache:true` basically means "do nothing" and will follow the normal cache behaviour (i.e. take the ressource from the first request); only `cache:false` will explicitly prevent caching (jQuery does this by appending random characters to the URL) – Bergi Sep 17 '14 at 15:34
0

I want JS-accessible image

Have you tried to CSS using jQuery? It is pretty fun - you have full CRUD (Create, read, update, delete) CSS elements. For example do image resize on server side:

$('#container').css('background', 'url(somepage.php?src=image_source.jpg'
    + '&w=' + $("#container").width() 
    + '&h=' + $("#container").height() + '&zc=1');

Surprisingly, I found that even when the javascript waits for the entire page to load, the image request still makes a new request! Is this a known bug in Firefox and Chrome, or something bad jQuery ajax is doing?

It is blatantly obvious that this is not a browser bug.

The computer is deterministic and does what exactly you tell it to (not want you want it to do). If you want to cache images it is done in server side. Based on who handles caching it can be handled as:

  1. Server (like IIS or Apache) cache - typically caches things that are reused often (ex: 2ce in 5 seconds)
  2. Server side application cache - typically it reuses server custom cache or you create sprite images or ...
  3. Browser cache - Server side adds cache headers to images and browsers maintain cache

If it is not clear then I would like to make it clear : You don't cache images with javascript.

Ideally I would hope the ajax wouldn't make a second request at all, since it is requesting exactly the same url.

What you try to do is to preload images.

Once an image has been loaded in any way into the browser, it will be in the browser cache and will load much faster the next time it is used whether that use is in the current page or in any other page as long as the image is used before it expires from the browser cache.

So, to precache images, all you have to do is load them into the browser. If you want to precache a bunch of images, it's probably best to do it with javascript as it generally won't hold up the page load when done from javascript. You can do that like this:

function preloadImages(array) {
    if (!preloadImages.list) {
        preloadImages.list = [];
    }
    for (var i = 0; i < array.length; i++) {
        var img = new Image();
        img.onload = function() {
            var index = preloadImages.list.indexOf(this);
            if (index !== -1) {
                // remove this one from the array once it's loaded
                // for memory consumption reasons
                preloadImages.splice(index, 1);
            }
        }
        preloadImages.list.push(img);
        img.src = array[i];
    }
}

preloadImages(["url1.jpg", "url2.jpg", "url3.jpg"]);

Then, once they've been preloaded like this via javascript, the browser will have them in its cache and you can just refer to the normal URLs in other places (in your web pages) and the browser will fetch that URL from its cache rather than over the network.

Source : How do you cache an image in Javascript

Is there a reason css and ajax in this case usually have different caches, as though the browser is using different cache storage for css vs ajax requests?

enter image description here

Even in absence of information do not jump to conclusions!

One big reason to use image preloading is if you want to use an image for the background-image of an element on a mouseOver or :hover event. If you only apply that background-image in the CSS for the :hover state, that image will not load until the first :hover event and thus there will be a short annoying delay between the mouse going over that area and the image actually showing up.

Technique #1 Load the image on the element's regular state, only shift it away with background position. Then move the background

position to display it on hover.

#grass { background: url(images/grass.png) no-repeat -9999px -9999px; }
#grass:hover { background-position: bottom left; }

Technique #2 If the element in question already has a background-image applied and you need to change that image, the above

won't work. Typically you would go for a sprite here (a combined background image) and just shift the background position. But if that isn't possible, try this. Apply the background image to another page element that is already in use, but doesn't have a background image.

#random-unsuspecting-element { 
    background: url(images/grass.png) no-repeat -9999px -9999px; }
#grass:hover { background: url(images/grass.png) no-repeat; }

The idea create new page elements to use for this preloading technique may pop into your head, like #preload-001, #preload-002, but that's rather against the spirit of web standards. Hence the using of page elements that already exist on your page.

Community
  • 1
  • 1
Margus
  • 19,694
  • 14
  • 55
  • 103
  • Yes I know you can css with Javascript, but that would be too odd and hacky in some circumstances - have to wait for jQuery, then js to load, then set it. Assuming you need image immediately shown, *and* access to the image programmatically for canvas/width/height use etc. preloading doesn't help. The browser *should* have all images loaded on-load, so it *should* be able to ajax request without downloading the whole thing (as it seems to do correctly in Chrome and older IE). – NoBugs Sep 24 '14 at 02:10
-2

The browser will make the 2 requests on the page, cause an image called from the css uses a get request (not ajax) too before rendering the entire page.

The window load is similar to de attribute, and is loading before the rest of the page, then, the image from the Ajax will be requested first than the image on the div, processed during the page load.

If u would like to load a image after the entire page is loaded, u should use the document.ready() instead

jefissu
  • 244
  • 3
  • 7
  • 1
    No, DOM ready is called first, then `window.onload`. And latter is called after loading all resources, also images. – algorhythm Sep 16 '14 at 20:11
  • @algorhythm Correction - load *should* be called after loading all resources, as you can see in the fiddle it doesn't have that url, at least not accessible to ajax without downloading the entire image again. – NoBugs Sep 17 '14 at 12:50
  • That's what I wrote: '...latter is called after loading all resources...'. – algorhythm Sep 17 '14 at 12:53