Interaction between jQuery .ready() and <script defer> - javascript

I am trying to figure out a problem with some code I have inherited.
I have an HTML page with
<script type="text/javascript" src="file1.js" defer="defer"></script>
<script type="text/javascript" src="file2.js" defer="defer"></script>
</body>
</html>
file1.js has
FOO = {
init : function () {
var bar = BAR;
}
}
$(document).ready(FOO.init);
file2.js has
var BAR = {
}
Because of the defer attribute on the elements, is it safe to assume that when the .ready() calls FOO.init() that BAR may still be undefined at that point b/c the code in file2.js hasn't executed yet because of the deferred execution?
This would match a bug I am trying to track down (only occurs sporadically in IE), but I really want to understand why this is happening before I work on a solution. I have no idea why the original developer used defer, other than a cryptic commend about "he had to" do it this way.

Defer should cause the script to be added to a queue that is processed after the page is completely loaded. According to the spec deferred scripts should be added to the queue in the order they came onto the page.
However different browsers have done slightly different things with the order. IE seems to run defer scripts in the order they finished loading rather than the order they occurred on the page. So you seeing the error sporadically because sometimes it's loading them in the right order and sometimes not.
See this post on hacks.mozilla.com for a more exhaustive explanation and examples of how different browsers handle the ordering of the defer queue.

Deffering in javascript gives preference to the browser of when to interpret the script, in some optimal conditions like with chrome the script is downloaded while the page is being loaded then parsed and interpreted. If you use defer like the above you can never be certain which script is loaded first or when the interpretation is complete.
BAR could be undefined on one page load and be defined on the reload (cached) or the second script was loaded first.
To test this try make a change to one of the scripts to force a new download and interpretation and see what race conditions exist.

Related

Are deferred scripts executed before DOMContentLoaded event?

Upon defer attribute MDN says:
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. The defer attribute should only be used on external scripts.
On DOMContentLoaded MDN also says:
The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets...
So DOMContentLoaded is fired before CSSOM is ready. This means deferred scripts are executed before CSSOM is ready. But if that's true the scrips must not be able to get correct CSS property values and must not apply CSS correctly. But it's not true, we know all deferred scripts work well.
So is MDN documentation technically incorrect?
Where can I find the official documentation of DOMContentLoaded`? I searched in https://dom.spec.whatwg.org/ but couldn't find it.
P.S: Please note that google says that CSSOM is built before executing any inline javascript
But Google is technically incorrect. Inline JavaScript gets executed before CSSOM is ready. And from my tests, I found that MDN is correct and if js files(both deferred and non-deferred) are downloaded before CSS files(or js is inline) then js is executed before CSSOM is ready. So js might handle styles incorrectly. To avoid that we need a forced reflow before all js logic.
So if a user visits our website with all js required already cached and CSS not cached OR js gets downloaded before CSS then (s)he might see an incorrectly rendered page. To avoid this we should add force reflow in all our websites' js files.
I didn't really read the spec though. The following are based on actual behaviors of Chrome (observed on Chromium 68, Ubuntu). Behaviors may vary among browsers, if they were just undefined in specifications. For example in 2010 scripts don't always wait for proceeding stylesheets. I assume agreements had been achieved and behaviors had been standardized over the years.
The defer scripts are executed after domInteractive, before domContentLoaded; it's sequential.
domInteractive and domContentLoaded are two timestamps which could be viewed in Chrome devtools' Performance (previously Timeline) tab. Probably also in other similar tools, but I haven't tried.
domInteractive is the point when HTML parsing and initial DOM construction are finished (and all "sync" scripts have finished executing). document.readyState changes from 'loading' to 'interactive'; a readystatechange event fires on document accordingly.
All defer scripts are executed in their appearing order. Then comes domContentLoaded, a DOMContentLoaded event fires on document.
DOM & CSSOM construction don't rely on each other; but sync scripts may introduce dependencies.
Each sync script, internal or external, waits for preceding stylesheets to be parsed (of course, after fetched).
Yes, sync scripts are not blocked by subsequent stylesheets. MDN and Google and other articles say "scripts depend on CSSOM to be ready"; they (probably) didn't mention that only preceding parts are depended.
P.S: Please not that google says that CSSOM is build before executing any inline javscript
Google didn't say that (at least, as of the time I read this article).
On the contrary, before one sync script is fetched (if external) and executed, any code following it, HTML, stylesheets or other scripts, can't be parsed/executed/constructed. They block anything subsequent to them.
So, in specific cases, eg. without sync scripts, DOMContentLoaded event may fire before or after CSSOM is ready. That's what MDN means by saying "without waiting for stylesheets".
defer/async scripts don't care about stylesheets at all.
Different from sync scripts, defer/async scripts don't wait for preceding stylesheets, and don't block subsequent stylesheets/scripts either. They are removed from those "dependency chains" completely. You can't rely on any proceeding stylesheets to have been parsed.
The differences between defer/async:
as stated above, defer scripts have predictable execution time; the DOM has been ready. They are also promised to execute in order.
Update:
defer scripts are added to the end of the list, says W3C's spec (the 20th item)
(also in WHATWG's spec)
async scripts have no promise on execution order; each async script would be "queued to execute" as soon as it is fetched; once the render process is idle, they are executed. (To be exact, different types of resources have different priorities. The spec provides precious requirements)
These should well explain hinok's two examples, the former async (from Google) and the latter defer。
I don't have much experience on working with CSSOM at page loading (I do operate on DOM at page loading, though), so I can't provide reliable advises. It seems "load event on window" or "force reflow early" might work.
I use deferred script loading. There was a lengthy technical explanation from some guy who is a well known website performance guru. He clearly states that deferred is the way to go (for this and that technical reason, backed by all kinds of data and charts, that many people seemed to feel was wide open for debate, re: async).
So I started working with it. Deferred scripts have the advantage of downloading async, but executing in the order presented, which can be a problem with async (e.g. you can load your app bundle before your vendor bundle because you don't control the execution order of async scripts just by saying "in this order").
However, I found out right away that although this solves that problem, this could mean, depending on how you grab your bundles, the CSS bundle isn't loaded. So you can end up with unstyled content, depending on how you set things up. Note that for defer, they also say that you shouldn't be writing to the dom etc. in those scripts (which again makes sense in terms of your documentation).
So it would seem your documentation is correct. The effect is easily reproduced.
How do I get out of it; the most basic way, is like this:
<script src="css.bundle.js"></script>
<script src="vendor.bundle.js" defer></script>
<script src="angular.bundle.js" defer></script>
<script src="app.bundle.js" defer></script>
This makes sure that the css loads in first, so your home page and so on will show up nicely, and also ensures that (although all three are loading async), that app.bundle will execute last, ensuring all other dependencies are in order.
So, you take the absolute bare minimum of CSS required to kick over the app, create that as a bundle, and load it first, before anything. Otherwise you can bundle in your CSS per module/component, and so on.
There's a lot more to this topic and I could probably be doing more, but again (I will try to find the reference), this was overtly recommended by that performance wizard, so I tried it and it seems pretty effective to me.
Edit: Fascinating, while looking for that reference (which I haven't found yet), I went through a handful of "experts" on the subject. The recommendations differ wildly. Some say async is far superior in all regards, some say defer. The jury really seems out on the topic, overall I'd say it probably has more to do with exactly how you build out your scripts than whether one is actually better than the other.
Edit again: Here's some more evidence. I ran a performance analyzer on a stub website using the above simple loading sequence, deliberately making the scripts naive so they'd be visible in a timeline.
Here's an SS of the result: there are four yellow boxes here. The first three are the evaluations of the scripts. The fourth one (when you mouse over it in the tool, this is just the SS remember) is the DOMContentLoaded event (the one with the red corner).
DOMContentLoaded can be fired before CSSOM, source
The domContentLoaded event fires shortly after the HTML is parsed; the browser knows not to block on JavaScript and since there are no other parser blocking scripts the CSSOM construction can also proceed in parallel.
Article on Google Developer describes async instead of defer but in the case of your question, it doesn't change anything because based on Steve Sourders article on perfplanet
DEFER scripts execute after DOM Interactive.
and his comment under his article
[...] the spec says that DEFER scripts run after domInteractive but before domContentLoaded.
You can make your own experiment, look below for code using defer and timeline
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.3/angular-material.css">
</head>
<body>
<h1>App</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/js/bootstrap.js" defer></script>
</body>
</html>

Not understanding this setTimeout in JS

I thought I understood how the setTimeout method worked, but this is confusing me.
test.html (I'm purposefully loading the test.js file before the jQuery file for demonstration. Let's say the jQuery file is hosted locally).
<body>
// ...code
<div id="area"></div>
// ...code
<script src="test.js"></script>
<script src="jquery.js"></script>
</body>
test.js
$('#area').text('hello');
I understand in this case "hello" won't get printed on the browser because jQuery is being loaded after the test.js file. Switching the order of these files solves the problem. But if I leave the order alone, and alter the test.js file, a setTimeout makes it work:
function wait() {
if(window.jQuery) {
$('#area').text("hello");
}
else
{
setTimeout(wait, 10);
}
}
wait();
In this case the "hello" text gets printed on the browser. But I'm sort of scratching my head because somehow the jQuery file does get loaded. But how? Why doesn't the test.js file get caught in an infinite loop forever checking to see if jQuery has loaded? I'd be grateful for some insight on the mechanics of what's going on.
There would be an infinite loop if jQuery never loaded. But in the normal case:
The first time through, jQuery isn't loaded, so we setTimeout()
1a. Other things happen in the meantime, including loading of resources like jQuery
10ms later, we check again.
Is jQuery loaded now? If not, set a timeout and go back to step two
After some number of retries, jQuery does load, and we're off.
The better way to do all of this, of course, would be to
Load jQuery first
Run your wait() function in a ready() handler so it doesn't run until it's needed.
<script src="jquery.js"></script>
<script src="test.js"></script>
// test.js
$(document).ready(
function()
{
$('#area').text("hello");
}
);
Why doesn't the test.js file get caught in an infinite loop forever checking to see if jQuery has loaded?
setTimeout works asynchronously. It does not pause the browser. It simply asks it to execute a certain function after a certain amount of milliseconds.
jquery.js gets loaded and executed inbetween wait() invocations.
Without that setTimeout() code, when the contents of "test.js" are evaluated the browser will immediately run into the problem of $ (jQuery) not being defined. With the setTimeout(), however, the code does not attempt to use the global jQuery symbols until it verifies that the symbols are defined.
Without the setTimeout the code fails with a runtime error. The code in the other version explicitly tests for that failure possibility to avoid it.
setTimeOut method runs in a separate queue called asynchronous callback. so once the interpreter comes to this line, the code is moved to a separate queue and continues with it parsing(which then executes jQuery.js). After this is executed , it looks for items the asynchronous queue to check if the timeout is completed and then executed method inside setTimeout. By this time jQuery.js is already loaded.
More on this
https://youtu.be/8aGhZQkoFbQ
JavaScript is not pre-compiled. It's working "on the fly".
You can add code on the fly, whenever you want, and this includes loading whole libraries. Once the browser loaded an external JS file it parses it, and it's all ready to use.
So if you wait for jQuery, and do have the proper code to load it, it will eventually be loaded by the browser and work.

jQuery undefined only in IE9 [duplicate]

I am sure I don't fully understand this problem, but it seems that we are seeing strange behavior on IE9 on my project, somehow related to out-of-order execution of JavaScript that has been injected via calls to document.write, e.g.:
document.write('<scr'+'ipt type="text/javascript" src="'+file1+'"></src'+'ipt>');
document.write('<scr'+'ipt type="text/javascript" src="'+file2+'"></src'+'ipt>');
document.write('<scr'+'ipt type="text/javascript" src="'+file3+'"></src'+'ipt>');
My limited Google research suggests that IE9 will execute scripts injected in this manner in a different order from other browsers (notably, Firefox and Chrome). Is there a better way to achieve what we're going for here, which will ensure the same execution order by all browsers?
I take that back: we don't really care about all browsers, just Chrome and IE9.
Use a script loader (like the one I wrote: LABjs), which will normalize all the different quirks of loading across the various browsers. And bonus: it doesn't use that god-awful document.write(). LABjs will let you load all your scripts asynchronously (in parallel), but make sure they execute in the proper order. Sounds like basically exactly what you want.
I guess you could chain the onload event of one to start the load of another:
var newJS= document.createElement('script');
newJS.onload=function() {alert("done")} //or call next load function
newJS.src="..."
document.body.appendChild(newJS)
So the advantage of writing script tags this way is that they are loaded asynchronously. I don't know about browser nuances about exactly how this is done but I would have thought they would be executed when they're downloaded, in no specific order. Similar to the behaviour of the HTML5 async attribute.
There's another HTML5 attribute defer which instead makes scripts execute in order, but in a non blocking way. You could try adding that into your generated <script> tags. IE9 partially honours it.
I have made a little script, exactly for this purpose:
https://github.com/mudroljub/js-async-loader
In short, it loads all your scripts asynchronously, and then executes them consequently. It looks something like this:
for (var lib in libs) {
loadAsync(lib);
}
And you don't need document.write();

Dynamic script addition should be ordered?

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
Adding the script element to the head directly with javascript. (Tests A -> D)
Adding the script element to the head using jquery's append() method. (Tests E -> H)
'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 http://dyn-script-load.appspot.com/images/dynScriptGraph.png
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 http://dyn-script-load.appspot.com/images/chromeDynScript.png
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 http://dyn-script-load.appspot.com/images/ffDynScript.png
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).
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);
}
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.
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).
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.
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.
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.

Is the load order for external javascript files different in IE8 compared to IE7?

I ask because I'm running an application in which I load an external script file in the HEAD section of the page, and then attempt to call a function from it in the onLoad section of the BODY tag.
external.js
function someFunction()
{
alert("Some message");
}
myPage.html
<html>
<head>
<script type="text/javascript" language="javascript" src="external.js"></script>
</head>
<body onLoad="someFunction();">
</body>
</html>
Using the developer tools in IE8, I get an exception thrown at the onLoad statement because, apparently, the external javascript file hasn't been loaded yet.
I haven't had this problem come up in IE7 before, thus my question.
Did they change the load order between IE7 and IE8? If so, is there a better way to do this? (the real function references many other functions and constants, which look much better in an external file)
Thanks,
B.J.
Well, I feel pretty stupid actually.
Turns out the problem wasn't with the load order. The problem was that the external javascript file had a syntax error in one of its functions, and apparently when the exception was thrown it completely invalidated the whole file, thus making the rest of the functions unavailable to the main page.
I'm not sure if this behavior is different in IE8 compared to IE7, but anyway, that was the real problem.
Thanks for your reply.
B.J.
I doubt very much that his has changed it would break a considerable number of websites.
Try this (without using the developer tools):-
<body onload="alert(somefunction)">
this shouldn't break and will tell you whether at the point onload executes whether the identifier somefunction can be seen.
Assuming that what you think is happening is what is happening, you should try to attach the body.onLoad later on.
To simplify things, you can do it with Prototype (including prototype, of course) with
Event.observe(window, 'load', function() { myFunction.init() });
or JQuery (including JQuery) with
$(document).ready(function(){
// Your code here...
});
I think there is a pure Javascript way to do this, but the problem is that the body element won't exist yet, so it's rough...
That said, I have had no problems running body onload in Javascript with IE8, and putting it right into the body tag, using external files. I'm going to test that right now out of curiosity, and I'll report back.
Edit: There's no problem doing the onload from an external file. However, while we're at it, you might want to get to know JQuery, Prototype or Scriptaculous :)

Categories