Preload, then execute

I talked before about using an object element to load scripts and styles without executing/applying them to the current document. And this is nice for preloading assets - faster and less error-prone than simple inclusion if the document or iframe.

But what about preloading assets (scripts) and then executing them on the current page? Turns out it works just fine, like so:

  • create an object element and point its data to the JavaScript file to be loaded
  • listen to the load event of the object
  • once loaded, create a script element and point its src to the file, which would be already cached
  • optionaly, execute a callback function once the script element is properly inserted into the DOM

Something like this:

function yield(file, callback) {
    var o = document.createElement('object');
    o.data = file;
        
    o.width  = 0;
    o.height = 0;
    
    o.onload = function() {
        var s = document.createElement('script');
        s.src = file;
        s.onload = function() {
            callback(o, file);
        };
        document.getElementsByTagName('head')[0].appendChild(s);
        
    };
    
    document.body.appendChild(o);
}

But, but.. why?

Well, first off it's yet another way to load JavaScript files asynchronously, in a non-blocking fashion. And that's a good thing. Async = win. This method sure beats others such as document.write (eek) or iframe. It has advantage over XHR because there's no same-domain restriction. DOM include is probably the only simpler and more robust method, but there are cases where the object element might be preferable.

DOM-included scripts in FF and Opera always execute in order. That's cool, but sometimes you may prefer out of order async execution. Whenever a script is ready - go! Or sometimes you may prefer if the script is not executed at all! That may sound odd (why would I load it if I don't want to execute it?) but I have a use case - auto-complete. When your data source is on another domain and you can't use XHR, then JSON-P would be the way to go. But the network is a jungle and anything can happen.

Say you make requests for auto-complete suggestions with queries "h", "he", "hel", "hell", "hello". For some reason "hel" is really slow and you already have the results for "hell" and even "hello". What you want to do is just kill and cancel the "hel" response, who cares - it's outdated already. But if you use JSONP in FF and Opera "hel" will block the others. If order is not preserved it might be even weirder - you update the page with the "hello" suggestion and then "hel" finally arrives and updates the suggestion once again. Fail.

Using the object element approach you can keep a list of requests that went out and simply choose to ignore previous requests if newer responses are already there.

I have a test case even.

Small print

This technique assumes that the scripts you load don't have any headers preventing caching, otherwise they'll be requested twice.

My example code above ignored IE completely, but you can easily make it work, either using new Image() instead of object or tweaking the object with height and width of 1 as shown in the old article. Or you can use the trick that LABjs uses (I think) which is to DOM-insert a script element with invalid type such as cache/javascript. Also you need to listen to onreadystatechange instead of load event when inserting a DOM element.

This entry was posted on Saturday, October 23rd, 2010 and is filed under JavaScript, performance. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.


Get notification for future posts: follow me on Twitter or subscribe to my RSS feed

13 Responses to “Preload, then execute”

  1. kangax Says:

    Hey Stoyan :)

    I actually don’t quite understand the benefit of preloading + script injection vs. just script injection.

    Doesn’t script injection already result in script being cached (considering the presence of proper headers)? Why go through all the trouble of creating object element (or Image), appending it to the document, waiting for its load event, and only then injecting a script, instead of just injecting the script? I understand if the goal is to prevent script execution (as you demonstrated in an earlier post) — then preloading makes sense. But this particular combination (object preloading + immediate script injection) benefit is not clear.

    Could you please explain?

  2. Kyle Simpson Says:

    FYI: Webkit has just recently landed a patch in nightly that prevents downloading scripts if the declared type attribute is an invalid/unrecognized type. This effectively breaks LABjs’ “preloading” trick. The change is precipitated by direct wording in the spec. It appears it’s quite possible that any other trick for preloading other than <link rel=prefetch> may, at some point soonish, be something that one or more of the browsers disables, based on what the current spec says about such behavior. It’s possible that may break the <object> and Image() tricks, as well.

    I’m currently in the process of petitioning W3C to change the spec to address the dynamic parallel load (but control execution order) use case, in a way that does NOT require the “preloading”.

    Moreover, as you noted, “preloading” in this way is kind of a hackish way to do things in the sense that if cache headers are improper, the resource will be re-loaded. And not only will there be a wasteful second request, but also the “immediacy” of executing the supposedly cached item will be lost too. That means if you’ve preloaded two or more items and then think you want to execute them in a certain order, this order will be a race condition because both resources may be in fact re-downloading instead of executing synchronously from cache.

    I would recommend against this trick (and hope to deprecate it from LABjs) unless you’re only preloading one script AND you are certain is sends out proper cache headers.

    ——
    Side note: It’s quite possible that an additional use-case exists which should be pushed for with W3C… which is just simply to load content but not execute it, and then be able to just directly execute that content later. For instance… a script node with a “donotexecute” attribute on it, and then calling scriptnode.execute() later when you want it to execute.

    But, like I said above, doing this by doing a double-load (assuming caching) is a brittle hack and proving to be the wrong path as browsers are closing that loophole per W3C spec.

  3. Eric D. Says:

    There may be a non-null risk : if people have a bad proxy in the company, or a stupid privacy software, they may download all script twice, even if you send the correct HTTP headers.

    If I follow the results of Figure 3 on http://yuiblog.com/blog/2007/01/04/performance-research-part-2 , these case are not so rare, so a preload relying only on http cache may be very risky.

  4. yanez Says:

    I’am working with this right now : )

  5. Dave Artz Says:

    Hey Stoyan, I couldn’t get the callback to work in IE (only tried 8 so far). Here’s my code:

    http://jsbin.com/atila3/3

    I also tried using Image() and the callback didn’t trigger there either:

    http://jsbin.com/uzipu4

    Am I missing something?

  6. Stoyan Says:

    Dave, the callback in IE doesn’t work because:
    - in the first example you need to subscribe to onreadystatechange event of the `object`, like with XHRs
    - in the second, subscribe to image’s onerror because the script is not a valid image

  7. Dave Artz Says:

    What am I missing?

    http://jsbin.com/atila3/6/edit

  8. Dave Artz Says:

    (besides a semicolon)

  9. Internet Explorer Dynamic Script Pre-Loading | Digital Fulcrum Says:

    [...] Script pre-fetching seems to be a hot topic these days.  Steve Souders’ ControlJS and LABjs from Getify both provide this capability and Stoyan Stefanov recently posted a mechanism for accomplishing this in FireFox. [...]

  10. When is a stylesheet really loaded? / Stoyan's phpied.com Says:

    [...] the object trick should do – all browsers seem to fire load and/or readystatechange event consistently. Obviously [...]

  11. טעינה מראש של קבצים לאחר טעינת הדף | FrontEnd.co.il Says:

    [...] הביצועים סטויאן סטפנוב מציע להשתמש בתג object ולטעון לתוכו את הכתובות. בשביל אקספלורר [...]

  12. programming Says:

    programming…

    [...]Preload, then execute / Stoyan’s phpied.com[...]…

  13. Eric Lawrence [MSFT] Says:

    http://blogs.msdn.com/b/ieinternals/archive/2012/05/05/problems-with-using-img-to-prefetch-script-or-css.aspx

Leave a Reply