Async Scripts – Cached?

September 24, 2012 12:25 pm | 4 Comments

I want to re-run the real user cache experiment that Tenni Theurer and I ran back in 2007. I’m designing the experiment now and will share that design in this blog when it’s well-baked. One change is I’d like to use an external script as the cached response that is tested. Another change is I want the experiment to be a snippet that any website can embed. That allows me to crowdsource results from websites with a wider range of session behaviors, user profiles, browsers, etc.

Since I’ll be asking websites to embed this 3rd party script, it’s important that it not harm performance and avoid frontend SPOF. I’ll do this using JavaScript to dynamically request the external script using the typical createElement-insertBefore pattern:

var newscript = document.createElement("script");
newscript.async = true;
newscript.src = document.location.protocol + "//";
var s0 = document.getElementsByTagName('script')[0];
s0.parentNode.insertBefore(newscript, s0);

But I wondered: Does loading an external script this way affect caching behavior?

I’ve been using this pattern for years and know that caching headers for dynamically-loaded scripts are respected in all the major desktop & mobile browsers, but what about other browsers? To answer this question I created a Browserscope user test last week. The Caching Async Scripts test page loads a script dynamically. The script has a far future expiration date so on the next page it should be read from cache. This is tested by measuring the script’s load time – normally it takes 6 seconds to load so if it’s far less than that it must have been read from cache.

I tweeted the test’s URL asking people to run the test. Thanks to everyone who ran it! The crowdsourced Browserscope results show data for over sixty browsers including Blackberry, Epiphany, and PlayStation. Happily, and surprisingly, it shows that every browser honors caching headers for scripts loaded dynamically. That’s great news in general, and with regard to re-running the cache experiment means that I can feel comfortable using this pattern to load the cached script while avoiding frontend SPOF.


4 Responses to Async Scripts – Cached?

  1. I have always wondered whether it’s better to write newscript.async = “async”; instead. Seems like it conforms to the spec and would work on browsers trying to evaluate to Boolean.

  2. @Nick according to specs:
    async = “async” or “” (empty string) or empty

    so setting it to true is enough :-)

    even semantically – it’s better

  3. “…it’s better to write newscript.async = “async”; instead. Seems like it conforms to the spec…”

    Actually, that’s incorrect. `async=”async”` is for markup attributes. WebIDL specifies that the reflected properties in JavaScript be true booleans, so setting to `true` or `false` is the proper way to go. Interestingly, setting something to the string “false” would still coerce to `true`, and could be confusing, so using strings is, I think, a bad idea. Just use booleans in JavaScript.

    And btw, in markup, there’s no need for a value at all. Just put `async` or `checked` or `disabled`, for those boolean attributes. Saves markup and potential confusion, because the same weirdness `async=”false”` in markup would still be interpreted as async being turned on.

  4. @Steve

    Also, please keep in mind that there are still a number of strange quirks around actual caching of dynamically loaded resources.

    In some circumstances, the browser is TOO aggressive and will more persistently cache a dynamically loaded resource than you would want. This has led to many frustrations for devs in browsers like Chrome (& others), where you do a shift+refresh, but dynamically loaded scripts are NOT revalidated, and thus keep using the old version.

    That form of the bug is tracked here:

    On the opposite side, there are cases where the browser SHOULD use the cache for a dynamically loaded resource, and it doesn’t, which just silently leads to more wasted loads/bandwidth and slower pages.

    That form of the bug is tracked here: