HTML5 script element - async attribute - when (and how best) to use? - javascript

Can you confirm my understanding of HTML5's <script async> attribute?
Any libraries referenced by other code in the page should not specify the async attribute.
For example, my script references might appropriately look like:
<script src="jquery..." /> <!-- async not used - ensure that this is loaded before JQuery UI and my code -->
<script src="jquery.ui..." /> <!-- async not used - ensure that this is loaded before my code -->
<script src="my_code1.js" async /> <!-- async used, for page load performance -->
<script src="my_code2.js" async /> <!-- async used, for page load performance -->
For any code in a $(document).ready(function () { } block, I can be assured that all async script have already loaded.
Do I have this right?

As with all new HTML5 features, I think the best way to find answers is to test them on as many current browsers as we can. As a general rule, old browsers should completely ignore the async flag, so code should work as expected, parsed from top to bottom in order.
As long as browsers are inconsistent in handling them, you should avoid using them in production code if you're not sure they will work.
The main question with this feature is, browsers that do support it, in what order do events get fired, for example if you define a jQuery ready function in an async loaded script, is it going to get fired? Does your ready event fire before or after async scripts have loaded?
I have created a couple of test files, you are quite welcome to have a play with them on different browsers to see how they behave.
Short Answer
About #Dave's assumption:
For any code in a $(document).ready(function(){} block, I can be assured that all async script have already loaded.
It doesn't look like it so far, it's pretty inconsistent. In Chrome the main ready event fires before the async file has loaded, but in Firefox it fires after it.
jQuery developers will have to make up their minds about this, if they will (and can) support it in the future or not.
Test Page
My test script spits out a string which shows you in what order were different parts of it executed. It can be built by the following:
D: It means the script block in the main file got executed. It can be
followed by :ok if the function in
the async loaded script if defined,
or :undefined if it's not.
R: It means the jQuery ready event in the main file got executed.
It can be followed by :ok if the
function in the async loaded script
if defined, or :undefined if it's
not.
L: Async loaded script file has been executed.
AR: jQuery ready event in the async loaded script has been
executed.
Test Results
Browsers supporting async:
Google Chrome 11.0.696.68: D:undefined R:undefined L AR
Firefox 4.0.1: D:undefined L R:ok AR
Browsers supporting async but tested without async (expecting same results):
Google Chrome 11.0.696.68: L D:ok AR R:ok
Firefox 4.0.1: L D:ok AR R:ok
Browsers NOT supporting async (expecting same results):
Internet Explorer 9: L D:ok AR R:ok
Opera 11.11: L D:ok AR R:ok
Test Script
test.html
<!DOCTYPE HTML>
<head>
<title>Async Test</title>
<script type="text/javascript">
var result = "";
</script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript" src="test.js" async></script>
<script type="text/javascript">
try{
myFunc();
result += "D:ok ";
} catch(ex) { result += "D:undefined "; }
$(function(){
try{
myFunc();
result += "R:ok ";
} catch(ex) { result += "R:undefined "; }
$('body').text(result);
});
</script>
</head>
<body>
</body>
</html>
test.js
// Fires straight away when this file is loaded.
result += "L ";
$('body').text(result);
// A test function to see if it's defined in certain parts of the main file.
function myFunc(){
return;
}
// A ready function to see if it fires when loaded async.
$(function(){
result += "AR ";
$('body').text(result);
});

This question has bothered me too for quiet some time now.
So I just finished writing "jqinit.js". The purpose of it is managing the dependencies in a way that you can just put them into your html as you did. And you can load jquery with async, too.
It works mainly by checking if jquery has been loaded and delaying execution of you script until it has. As a bonus you can manage dependencies between your scripts, too. And it can be loaded async itself.
Have a look if it fits your needs (feedback welcome): https://github.com/ScheintodX/jqinit.js

Related

Javascript with the "async" attribute - can it change DOM elements that are defined below it?

The body of my HTML:
<body>
<script async src="changeParContent.js"></script>
<script> \\some irrelevant inline code that takes a long time to run
</script>
<p id='par1'>Initial text</p>
</body>
And here is changeParContent.js:
document.getElementById('par1').innerHTML = "The content was changed!"
I expected the Javascript to fail, since I use the async attribute. It means that the script will be downloaded and executed while the HTML is being parsed. Now, since I intentionally slow down the HTML parsing, I expected the javascript to run before the paragraph "p1" was defined.
I thought that I did the one thing you're not supposed to do - defining the DOM elements that are used in the async script below the script.
But it somehow works and the paragraph changes. How can it be?
Async scripts don't really run in any predictable fashion, and it's mainly down to the browser's implementation. For example, you should find the the following works in Chrome:
<body>
<script async src="./changeParContent.js"></script>
<script>
function sleepFor(sleepDuration){
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){ /* Do nothing */ }
}
sleepFor(5000);
</script>
<p id='par1'>Initial text</p>
</body>
However in Firefox the following error is thrown:
Uncaught TypeError: document.getElementById(...) is null
It seems that in both browsers, the slow inline script blocks both the async script from running and the rest of the DOM from loading. Then after this slow script runs, the async script runs in parallel with the rest of the DOM loading. In my experiments, it seems that in Chrome the race is won by the DOM, which causes no errors as the element is loaded by the time the async script runs. However in my Firefox test, the race is won by the async script which throws an error.
I think the lesson here is to trust best practices, even if it looks like there's no issues with your code.

Using async not working $ is not defined

I am having a error if I used async in script tag like below
<script async src="main.js"></script>
The error shows only on chrome saying
Uncaught ReferenceError: $ is not defined
If I removed the async from the script tag there is no more error in my console and everything works fine.
Do you have any idea why am having this problem ?
EDIT
Below script are placed inside the head tag
<!-- JS -->
<script async src="../js/jquery/jquery-1.10.1.min.js"> </script>
<script async src="../js/vendor/modernizr-2.8.2.min.js"></script>
<script async src="../js/asynchronous-resources/2014-06-03-asynchronous-resources.js"></script>
<!-- IE JS -->
<!--[if !IE]><!--><script async src="../js/ie10.js"></script><!--<![endif]-->
main.js is added to the footer.
<script async src="../js/main.js"></script>
I have found a similar question on stackoverflow.
Load jquery asynchronously before other scripts
I had to change async to defer there is no more issue now in firefox, chrome and IE9.
Byt it breaks in IE8 and IE7 completely. jQuery stopped working if I use defer.
Okay.....
So Basically...
WITHOUT ASYNC:
JS files are downloaded and executed sequentially IN ORDER ... i.e., The first encountered JS file gets downloaded and executed first, then the next and so on, while at this time the HTML parser is blocked which means no further progress in HTML rendering.
WITH ASYNC:
JS files[all] are put to asynchronous download as they are encountered, and are executed as soon as they get fully downloaded. HTML parser is not blocked for the time they are downloaded. So the HTML rendering is more progressive.
DISADVANTAGE:
However, the problem with asynchronous download and execution is that the JS files are executed as soon as they are downloaded... i.e., NO ORDER is maintained..for example, a smaller JS file(main.js) that gets downloaded before a larger file(jQuery.js) gets executed before the larger file.
So, if my smaller file has reference to some variable / code ($ of jQuery) initialized in the larger file, alas, the code has not been initialized yet and therefore an error is thrown. And that is what is happening here.
WHAT SHOULD YOU DO:
1> Remove async attribute and live with a lower performance.
2> Use dynamic loading of scripts which maintains the order of execution. Dynamic scripts are downloaded asynchronously by default but are executed seperately from the DOM parsing, therefore not blocking the HTML rendering. In this, by writing
script.async = false;
we force these to get downloaded and executed IN ORDER.
<script type="text/javascript">
[
'jQuery.js',
'main.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
</script>
Had the same problem but wanted to keep the async for better performance. I've fixed the problem by moving all code from main.js into an onload function. Like so:
window.onload = function () {
// main.js code
};
This way the main.js code only runs after the page is loaded (including the jQuery).
UPDATE: Here's another (better?) way:
var myScript = document.createElement('script');
myScript.src = 'http://code.jquery.com/jquery-1.9.1.min.js';
myScript.onload = function() {
console.log('jQuery loaded.');
};
document.body.appendChild(myScript);
Your main.js is getting executed asynchronously even before jquery is loading synchronously.
Either add async attribute to jquery as well or remove async attribute from main.js
MDN
Set this Boolean attribute to indicate that the browser should, if
possible, execute the script asynchronously. It has no effect on
inline scripts (i.e., scripts that don't have the src attribute).
I just finished writing jsinit.js just for this purpose.
It enables you to use async even with jQuery. It works by delaying execution of your module until jquery has finished loading. (And it can be loaded "async" by itself!)
Have a look: https://github.com/ScheintodX/jqinit.js
This way work fine on ggle chrome for me :
Replace
<script async src="JS/jquery-3.1.1.min.js"></script>
<script async src="JS/my.js"></script>
by
<script defer src="JS/jquery-3.1.1.min.js"></script>
<script defer src="JS/my.js"></script>
it charge first jquery and after my.js like the order
My guess is main.js has some jQuery in it. The async tag will force the browser to parse the JavaScript as soon as it is available, which may be before the jQuery script is ready.
From W3schools:
When present, it specifies that the script will be executed
asynchronously as soon as it is available.
To solve:
Add the async attribute to your jQuery in addition to main.js
Remove the async attribute from main.js
Remove the async tag altogether
If you provide some more information on what you're using the async tag for I can offer some more suggestions.
To solve this problem you can simply write all of the inline code as a function. Then add the onload attribute to the script tag for the async jQuery like so:
<script src="../scripts/jquery.min.js" async onload="myFunction()"></script>
Make sure that in the head section of your html myFunction is placed before the async script tag.

Uncaught ReferenceError: jQuery is not defined only in Chrome

I use this code to load my JS async in the head
<script type='text/javascript'>
// Add a script element as a child of the body
function downloadJSAtOnload() {
var element4= document.createElement("script");
var element5= document.createElement("script");
element4.src="http:///ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
element5.src="http://yourjavascript.com/301810712121/slidemenu_horiz.js"
element4.async=true;
element5.async=true;
document.body.appendChild(element4);
document.body.appendChild(element5);
}
// Check for browser support of event handling capability
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>
In IE and Firefox works fine, but in Chrome I have this error:
"Uncaught ReferenceError: jQuery is not defined "
When I refresh the page for second time (or third) the script works fine in Chrome, please I need to know how to resolve this.
Given your needs, and because I've used it successfully in the past I'd suggest using LABjs - http://labjs.com/
As mentioned, there are loads of script loaders to choose from - LABjs is focused on performance more than anything and doesn't include many of the extra features that others such as requirejs (AMD loader), YepNope (feature detection-based conditional loader) have. If all you need is to load your scripts asynchronously and have control over the execution order, LABjs is a very small script that handles this well.
Using LABjs, you'd do the following to replicate your code above:
<script src="js/libs/LAB.js"></script>
<script>
$LAB
.script('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js').wait()
.script('http://yourjavascript.com/301810712121/slidemenu_horiz.js')
.wait(function () {
// Check jQuery has loaded (could do this for the slider as well)
if (window.jQuery) {
// Do something with your slider
}
});
</script>
In the example above, the .wait() function ensures that jQuery has executed before slidemenu_horiz.js - the last .wait() is passed an anonymous function as a callback - within this you can test that everything has loaded and then do your initialisations.
It's worth checking out all the options as far a script loaders go. There really are loads out there and each has a different feature set that you may find addresses your problem better.
EDIT: Added script reference to LABjs in code example for clarity

Use JavaScript to prevent a later `<script>` tag from being evaluated?

This is a bit of an oddball use case, but I have my reasons:
I'd like to be able to write
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
in my markup and, using the code in first.js, prevent or delay the execution of second.js. Is this possible, in any browser? What if the contents of first.js are inlined? (If it helps, assume that the second script tag has an id attribute.)
Since I've gotten a couple of answers that missed what I'm getting at, I should clarify:
The solution must be entirely within first.js. Anything that require changes to the original HTML of the page, or to second.js, is not acceptable.
It is acceptable to load second.js via Ajax and execute it using eval. That's the easy part. The hard part is preventing the immediate execution of second.js.
Assume that you don't know what's in second.js. So, you can't just replace each global function called by second.js with a no-op function. (Plus, this would almost certainly lead to errors.)
If you know of a solution that works in some browsers but not in others, I'd love to hear it.
Example: To make this a little more concrete, let's say that the code
<script type="text/javascript">
function func() {
window.meaningOfLife = 42;
window.loadSecond();
};
setTimeout(func, 10);
</script>
precedes the two script includes, and that second.js contains the line
if (window.meaningOfLife !== 42) {throw new Error();}
first.js should be able to prevent this error by delaying second.js from executing until window.loadSecond is run. (Assume the implementation of window.loadSecond is also in first.js.) It is not allowed to touch window.meaningOfLife.
Update: Alohci's answer meets these requirements, but only on the condition that the second script tag comes immediately after the first, with nothing but whitespace in between. If someone could extend his hack to avoid that requirement, without introducing other unwanted consequences, that would be amazing...
Given your specific requirements set, this is actually quite simple and should work completely cross-browser. It does require however, that first.js immediately precedes second.js without anything between them except white space.
First, let's assume that the HTML looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Test Case</title>
<meta charset="UTF-8" />
<script type="text/javascript">
function func() {
window.meaningOfLife = 42;
window.loadSecond();
};
</script>
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
</head>
<body>
<p>Lorem ipsum dolor sit amet ...</p>
Run Func()
</body>
</html>
I've removed the setTimeout because that can cause func() to run before start.js runs causing a "loadSecond is not defined" error. Instead, I've provided an anchor to be clicked on to run func().
Second, let's assume that second.js looks like this:
document.body.appendChild(document.createTextNode("second.js has run. "));
if (window.meaningOfLife !== 42) {throw new Error();}
Here, I've just added a line to append some text to the document body, so that it is easier to see when second.js actually runs.
Then the solution for first.js is this:
function loadSecond()
{
var runSecond = document.createElement("script");
runSecond.setAttribute("src", "second.js");
document.body.appendChild(runSecond);
}
document.write("<script type='application/x-suppress'>");
The loadSecond function is just there to run second.js when func() runs.
The key to the solution is the document.write line. It will inject into the HTML <script type='application/x-suppress'> between the close script tag of first.js and the open script tag of second.js.
The parser will see this and start a new script element. Because the type attribute has a value which is not one that the browser recognises as being JavaScript, it will not attempt to run its content. (So there are an infinite number of possible type attribute values you could use here, but you must include a type attribute, as in its absence, the browser will assume that the script's content is JavaScript.)
The second.js script's opening tag will then be parsed as text content of the new script element and not executed. Finally the second.js script's closing tag will be re-purposed to close the new script element instead, which means that the remainder of the HTML is parsed correctly.
You can see a working version at http://www.alohci.net/static/jsprevent/jsprevent.htm
In first.js, set var shouldILoad = true
Then, load second.js this way:
<script>
if (shouldILoad) {
(function() {
var myscript = document.createElement('script');
myscript.type = 'text/javascript';
myscript.src = ('second.js');
var s = document.getElementById('myscript');
s.parentNode.insertBefore(myscript, s);
})();
}
</script>
(where 'myscript' is the ID of some element before which you'd like to insert the new Script element)
As far as I know, you can't. If the markup looks like
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
you can't access the second script element from within first.js, as it hasn't been added to the DOM at the moment the first script runs (even not if you assign an id to the second element). It doesn't matter whether the code of second.js is put inline or in an external file.
The only thing I don't understand is your second point. First you say that you can't control the markup of the document, but then you state it is possible to load second.js dynamically (using AJAX).
Following article describes the way you could block (3-rd party) scripts loading/execution from your script (including the both tag in the page head and dynamically added tags).
To handle existing tags on a page:
Use a MutationObserver to observe script elements insertion and inside the MutationObserver callback backup the script (to enable/insert it later) and change the script type to "javascript/blocked" (not works in IE, Edge, Firefox). Also you could handle deprecated (but working) beforescriptexecute event in Firefox to prevent script load.
Manually set type "javascript/blocked" (works everywhere including IE and Edge) like
<script type="text/javascript" type="javascript/blocked" src="second.js"></script>, then backup it in MutationObserver callback and re-add it later.
To handle dynamically added tags
Monkey-patch the document.createElement.
Override ‘src’ and ‘type’ descriptors on the HTMLScriptElement prototype.
Also this guys provide a yett library with the approach described in the article.
All <script> tags have their own execution context, which makes it nearly impossible to interfere with each other. Of course you've got the (infamous) global object (referenced by window in browsers).
Preventing the execution of second.js is rather simple: break it!
Assuming that second.js tries to call document.getElementById for example:
Working example of breaking jQuery, then loading later (with dependecies).
Tested on: IE 6+, FF 3.6+, Chrome
end of first.js
var execute;
// saving our position
var scripts = document.getElementsByTagName("script");
var i = scripts.length;
// breaking getElementById
var byId = document.getElementById;
document.getElementById = null;
var interval = setInterval(function () {
if (i != scripts.length) {
var second = scripts[i];
// stop polling
clearInterval(interval);
// fix getElementById
document.getElementById = byId;
// set the delayed callback
execute = function (onload) {
var script = document.createElement("script");
script.src = second.src;
script.onload = script.onreadystatechange = onload;
document.getElementsByTagName("head")[0].appendChild(script);
};
}
}, 100);
anytime you wanna execute second.js
execute(function(){
// second.js dependant code goes here...
});
Note: the onload parameter for execute is optional.
Just to see if this was possible, I had first.js send a synchronous XHR to a PHP file, and had the PHP file delete second.js. When the readyState reached '4', I had the JS alert something, to stop the thread. Then I went and checked the server... Yeah, second.js was deleted. And yet, it wouldn't work. I'd close the alert box, and the code that was in second.js would still be executed, despite the fact that the file was gone.
I don't really know what this means, but the answer to your question is probably, "No, it's not possible."
you may use setTimeout() to delay the execution of some code

How to dynamically load Javascript files and use them right away?

I am using JQuery to inject dynamically script tags in the body tab of a webpage. I got something like :
function addJS(url) {
$("body").append('<script type="text/javascript" src='+url+'></script>');
}
I add several scripts this way, and try to use them right after. E.G :
lib.js
function core() {...}
alert("I'am here !");
init.js
addJS("lib.js");
c = new core();
test.html
<html>
<head>
<title>test</title>
<script type="text/javascript" src="init.js"></script>
</head>
<body>
Hello
</body>
</html>
Loading test.html pops up "I'm here" and then ends up with an error "core is not defined". Of course merging both of the JS files will make them work perfectly.
I just don't get it o_O.
EDIT
I simplified this example, but Jeff answer made me understand that it was a mistake. So here are some details :
init.js is not in the head of test.html when it reload because I inject it with a code exectuted on a bookmarklet.
So the real execution process is the following :
reload test.html > run the bookmarklet > jquery and init.js are inserted > lib.js is inserted
Sorry for the confusion.
EDIT 2
Now I have the solution to my problem (that was quick :-)) but I am still interested to the answer to my question. Why does this go wrong ?
jQuery has this functionality built in with getScript.
You get the "core is not defined" error because the scripts are loaded asynchronous. Which means that your browser will start loading lib.js in the background, and continue executing init.js, and then encounter "new core()" before the lib.js has finished loading.
The getScript function has a callback that will be triggered after the script is finished loading:
$.getScript('lib.js', function() {
var c = new core();
});
Notice your addJS function is appending to the end of the body element.
Since browsers will run scripts as they appear in the HTML source,
c = new core()
will run before your lib.js script is loaded (at the end of the body element).
I would recommend moving c = new core(); into the $(document).ready(function() {...}); or into a script element AFTER the body tag.
IMO, appending the script tag to the end of the document to load a script is rather unsightly. Reason:
you are trusting the browser to automatically fetch the script and load it.
You have no way of finding out whether the script is loading, has loaded, or if it encountered an error (maybe 404?)
The appropriate way would be to either use $.getScript(), or for a finer-grained control, fetch the script file with $.ajax() and use eval().
However, the second method has some issues: if you invoked eval() inside a function, then the script won't be available outside it! This mandates workarounds...
but why bother! use $.getScript() and get over with it :)
cheers, jrh.
In response to why your code is failing: Adding a script tag to the body does not block further script execution. Your code adds it, which starts the browser download process. Meanwhile, your script tries to call core(), which doesn't exist because lib.js hasn't finished downloading. jQuery works because it waits till the script finishes downloading before executing your callback function.

Categories

Resources