views:

227

answers:

9

Hi,

I'm adding some <script> tags dynamically to the head element after page load. I understand the scripts are loaded asynchronously, but can I expect them to be parsed in the order they are added?

I'm seeing the expected behaviour in Firefox, but not in Safari or Chrome. Looking at the document in Chrome developer tools and Firebug, both show the following -

<html>
  <head>
    ...
    <script type="text/javascript" src="A.js"></script>
    <script type="text/javascript" src="B.js"></script>
  </head>
  ...
</html>

However looking at the resource loading view, chrome seems to parse whichever is returned first from the server, while firebug always loads them in the order the script tags were added, even when B is returned first from the server.

Should I expect Chrome/Safari to parse the files in the specified order? Using Chrome 5.0.375.29 beta on OS X 10.6.3

EDIT (10/5/10): When I say parse, I mean execute - can see many benefits of aggressive parsing - thx rikh

EDIT (11/5/10): Ok so I put together a test along the lines of that by juandopazo below. However I have added a combination of things, including

  1. Adding the script element to the head directly with javascript. (Tests A -> D)
  2. Adding the script element to the head using jquery's append() method. (Tests E -> H)
  3. 'Loading' the script with jquery's getScript() method. (Tests I -> L)

I also tried all combination of the 'async' and 'defer' attributes on the script tags.

You can access the test here - http://dyn-script-load.appspot.com/, and view source to see how it works. The loaded scripts simply call the update() function.

The first thing to note, is that only the 1st and 3rd methods above operate in parallel - the 2nd executes requests sequentially. You can see a graph of this here -

Image 1 - Graph of Request Lifecycle
Request lifecycle Graph

It's also interesting that the jquery append() approach also blocks getScript() calls - you can see that none of them execute until all of the append() calls are complete, and then they all run in parallel. Final note on this is that the jQuery append() method apparently removes the script tags from the document head once they have executed. Only the first method leaves the script tags in the document.

Chrome Results

The results are that Chrome always executes the first script to return, regardless of the test. This means all the test 'fail', except the jQuery append() method.

Image 2 - Chrome 5.0.375.29 beta Results
Chrome Results

Firefox Results

On firefox, however, it appears that if the first method is used, and async is false (i.e. not set), then the scripts will reliably execute in order.

Image 3 - FF 3.6.3 Results
FF Results

Note that Safari seems to give varied results in the same manner as Chrome, which makes sense.

Also, I only have a 500ms delay on the slow script, just to keep the start->finish time down. You may have to refresh a couple of times to see Chrome and Safari fail on everything.

It seems to me that without a method for doing this, we are not taking advantage of the ability to retrieve data in parallel, and there is no reason why we shouldn't (as firefox shows).

A: 

As I understand it, they are meant to be executed in the order they appear in the document. Some browser might be able to perform some parsing out of order, but they would still have to be executed in the correct order.

rikh
This was my understanding as well.
hawkettc
A: 

No, you cannot expect that all browsers will defer execution of both scripts until both are loaded (**especially when you are adding them dynamically).

If you want to execute code in B.js only after A.js is loaded then your best bet is to add an onload callback to A.js that sets a variable and another one to B.js that checks to see if that variable has been set, then it executes the necessary function in B.js if it has (and if A.js has not loaded, it starts a timer that periodically checks until it has loaded).

Sean Vieira
A: 

The download order and the execution order is not the same thing. In your page, even if B.js is downloaded first, the browser's engine will wait for A.js to continue processing the page.

The scripts are definitely processed, not only in the order they appeared in the document, but also at the place they appeared.

Imagine if it wouldn't be like that, there would be many errors if your little script that uses jQuery is downloaded and processed before the jQuery library.

Also, when you do a "document.write" in a js file, it appears where the script has been declared. You can't access DOM objects that are appearing after the script declaration neither.

This is why there are recommendations to put scripts at the very bottom of the page, to prevent their execution too soon and decrease the "perceived load time" of the page, because the browser's rendering engine is stopped as soon as a script is processed.

Mike

EDIT: if they are added dynamically with javascript, I think they are processed in the order they were added in time.

Mike Gleason jr Couturier
Great this is what I was expecting - so if I am seeing the scripts *not* executed in the order that they appear in the document (which is also the order in which they were added in time), then this would be a bug in WebKit?I've got four things telling me what's going on (1) The actual structure of the DOM showing the scripts in the right order (2) The resource load view in the dev tools showing the second script being received first (3) The console log showing the order the scripts are being added (in time), and (4) the console log showing the second script being executed first
hawkettc
Maybe at the last line of A.js, put a line to include B.js via a "document.write"?
Mike Gleason jr Couturier
The main goal is to load the scripts in parallel, but execute in the order they were added. This works in Firefox, but not in chrome/safari. I can use the 2nd approach in the test (see edit in main post) to guarantee correct execution order in all browsers, but it's not concurrent. I'm thinking that rather than take your suggested approach to get serial loading (and thus execution), I would go with jQuery.append(), as no extra code is needed in the scripts themselves. That said, I have serial load working right now - I'm looking for a concurrent load solution to improve performance - cheers.
hawkettc
A: 

You could load b.js from a.js to be 100% sure ... although I'd like the definitive answer to this question myself, especially with sync ajax loading of scripts.

James Westgate
That definitely works - I'm in the process of moving away from doing that, to get the performance benefits of loading the scripts concurrently :)
hawkettc
A: 

I was investigating this while working on a little library that loads modules dynamically like YUI 3. I created a little test here that loads two scripts that just insert content into divs. One is a common JS file and the other is a PHP file that waits 3 seconds to execute.

http://www.juandopazo.com.ar/tests/asyn-script-test.html

As you can see, scripts are executed when they finish loading, and not in the order in which you append them to the DOM, in every browser.

juandopazo
I had a look at the DOM structure for the test, and noted a couple of things - firstly the scripts are in the body rather than the head, and the second script is listed first - so if ordering were honoured, you would expect script 2 to execute first - good test though would be good to see the results when the script tags are in order. ta
hawkettc
Right. Sorry, I forgot to put them in the head. They were in the wrong order because I used insertBefore() instead of appendChild(). I changed it and the result is the same.You should be looking at writing some sort of a queue. Ask for modules in different files that call methods from a global object. Take a look at how YUI3 works.The scripts with your code usually looks like:YUI().use('module1', 'module2', function (Y) { //do something});And each module starts:YUI().add('module1', function (Y) { // add your stuff to Y});
juandopazo
Thanks for this - I haven't got enough reputation to mark up sorry. Just trying to work out why I am seeing firefox behaving as expected in my scenario, but not in yours. Looks like I've probably misread my situation.
hawkettc
I'm looking at the 'asynch' attribute that you are adding to the script tag. Reading here - http://www.whatwg.org/specs/web-apps/current-work/#attr-script-async, it seems clear that it will affect when the script is executed, but it's tough to get a clear picture of what to expect in the scenario where scripts are added dynamically. Will do some testing to see what happens with these attributes.
hawkettc
+1  A: 

Sorry for answering my own question, but its been a while and we did come up with a solution. What we came up with was to load the javascript concurrently as text contained in a json object, and then used eval() once they were all loaded to execute them in the correct order. Concurrent load plus ordered execution. Depending on your use case you may not need the json. Roughly, here is some code that shows what we did -

// 'requests' is an array of url's to javascript resources
var loadCounter = requests.length;
var results = {};

for(var i = 0; i < requests.length; i++) {
   $.getJSON(requests[i], function(result) {
      results[result.id] = result;
      ...
      if(--loadCounter == 0) finish();
   });
}

function finish() {
  // This is not ordered - modify the algorithm to reflect the order you want
  for(var resultId in results) eval(results[resultId].jsString);
}
hawkettc
A: 

Hello All How do you expain that if i host the HTML page under http://dyn-script-load.appspot.com/?delay=0.75, while still targeting dyn-script-load.appspot.com/get_js :

loadScript(id, "http://dyn-script-load.appspot.com/get_js?delay=" + delay + "&id=" + id, async, defer, method, function() { $("#" + id).css("color", "red"); });
  loadScript(id, "http://dyn-script-load.appspot.com/get_js?id=" + id, async, defer, method, function() { $("#" + id).css("color", "green"); });
}

Google Chrome (5.0.375.127) behaves badly (ie no method can garantee the order, second script is always loaded first, whatever the method used). The test page is http://integration.vttrack.fr/load.html

While the same instance of google chrome works OK with method=1 on http://dyn-script-load.appspot.com/?delay=0.75

Other confirm the behavior i see ?

My goal is to garantee javascript load and on my server using method=1 (so that it is ok with FireFox and Chrome). But it nevers works constantly (even with FireFox), so i began to make http://dyn-script-load.appspot.com/?delay=0.75 to work on my server, just to see if i had done some nasty mistake !

Thank you for your reply. By the way i have star'ed http://code.google.com/p/chromium/issues/detail?id=46109

Florent
You probably want to ask your own question, and just add a reference to this one.
sje397
I'm not quite sure I understand the question. Are you wondering why method 1 guarantees ordering? As noted above, this works in all browsers, because the requests are done sequentially, so they will always load in the order that you add them. The question makes this clear, and shows the timing graph with sequential behaviour for this method. This approach is not good, because we lose the benefit of concurrent retrieval.
hawkettc
A: 

so the question is how behaves Chrome on http://integration.vttrack.fr/load.html for you ?

Florent
This should be a comment - the bit on the page titled 'Your Answer' is for *answers*
sje397
@sje397: commenting requires rep, of which @Florent has none.
Crescent Fresh
A: 

@hawkettc :

look at http//integration.vttrack.fr/load.html which has the same HTML/Javascript as you in http//dyn-script-load.appspot.com/

Use it with Google Chrome, you will see that even with method 1 the ordering is not garanteed ! I don't see any reason :(

Sorry as a new user i can not post a new URL so that i wrote http//

Florent
@Florent I can see the problem you are referring to. I have taken some time to look into it, and haven't been able to identify the cause. I agree the js files are the same. Looking at the timing graphs, one is sequential as described in this thread, but yours is concurrent. I'm using Chrome 6.0.472.51 beta. My best guess at the moment is some caching behaviour I'm not considering. I'm interested to know if you discover the cause. Thx.
hawkettc
Looking at the js file, the only difference is that the call to get_js is not from the same origin as the original page. So while I said they were the same in the previous comment I was ignoring this. I'm wondering if this actually the cause of the variant behaviour - whether the same origin policy in the browser is affecting the caching behaviour perhaps. I don't know enough about this stuff to be sure - but the sequential behaviour of method 1 is client side, not server side, so need to look at why the browser is behaving differently.
hawkettc
Yes for sure origin policy is somewhat to look in. In real use case the javascripts are loaded from external servers (in my application this is the case). Just to note that FireFox behaves OK with method 1 and with differnent origins, so again the browser is making the difference. Just one question : is there a way to wait for script loading to trigger the next script loading that can be more Browser independant ?
Florent
Did you see the answer I provided above? Just load the resources as text and then eval() them in the order you require. You could do this in parallel or in sequence e.g.1. load file A2. eval() file A3. load file B4. eval() file Bwould offer sequential loading and evaluation.
hawkettc
Florent
@florent If you are just retrieving javascript, rather than json, then don't use $.getJSON(), use $.get() or $.ajax().
hawkettc