JavaScript to detect when external javascripts are loading - javascript

Is there a way (event listener or otherwise) to detect when a particular external javascript is about to load / is loading / has finished loading?
In otherwords, does the browser fire an event when it's about to load, is loading, and/or has finished loading a particular external script?
For my purposes it's not enough to simply check to see if a known object exists or anything like that. Instead, I need something that will detect a JS file is loading/loaded regardless of the contents of the JS file.

The following example works in Chrome. It attaches an handler on the onload event of the head tag and then adds an external javascript file. When the file is loaded, the event is captured and an alert appears.
http://jsfiddle.net/francisfortier/uv9Fh/
window.onload = function() {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", "http://code.jquery.com/jquery-1.7.1.min.js");
head.addEventListener("load", function(event) {
if (event.target.nodeName === "SCRIPT")
{
alert("Script loaded: " + event.target.getAttribute("src"));
}
}, true);
head.appendChild(script);
}

Since all browsers blocks the "UI thread" when processing <script> tags, you can rely that pre-existing tags are loaded.
If you are loading a script dynamically, you can listen to the load event of the <script> tag.
function loadScript(src, callback) {
var script = document.createElement("script");
script.setAttribute("src", src);
script.addEventListener("load", callback);
document.getElementsByTagName("script")[0].insertBefore(script);
};
loadScript("http://code.jquery.com/jquery-1.7.1.min.js", function(){
alert("loading is done");
});

<script onload> will fire when a script is finished loading.
You will not be able to do something like:
<script src="/foo.js"></script>
<script src="/bar.js"></script>
<script>
function alertonload(src){
console.log(src+' is loaded');
}
scripts = document.getElementsByTagName('script');
for(var i=0; i<scripts.length; i++){
scripts[i].onload = function(){ alertonload(scripts[i].src); };
}
</script>
This is pure conjecture and speculation; I have not tried it and there's probably better ways to write it, but this will not do what you're looking to do. EDIT: The scripts are loaded as the browser sees them, not after the fact. They will be loaded before this occurs.

I wrote a script for those who wants to call a function after all external files (dynamically added) are loaded. It goes like this:
var file = [];
var loaded = [];
var head = document.getElementsByTagName('head')[0];
var fileOnLoad =
// Pass the arrays to your function
(function(file, loaded){ return function(){
loaded.push(true);
// Print the number of files loaded
document.getElementById("demo").innerHTML +=
"<br>"+loaded.length+" files loaded";
if(file.length == loaded.length){
alert("All files are loaded!");
}
}})(file, loaded);
////////////////////////////////////////////////
//// ////
//// Add the external files dynamically ////
//// ////
////////////////////////////////////////////////
file[0] = document.createElement('script');
file[0].src =
"https://maps.googleapis.com/maps/api/js?v=3.exp";
file[0].onload = fileOnLoad;
head.appendChild(file[0]);
file[1] = document.createElement('script');
file[1].src =
"http://code.jquery.com/jquery-latest.js";
file[1].onload = fileOnLoad;
head.appendChild(file[1]);
file[2] = document.createElement('script');
file[2].src =
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js";
file[2].onload = fileOnLoad;
head.appendChild(file[2]);
file[3] = document.createElement('link');
file[3].rel = "stylesheet";
file[3].href =
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css";
file[3].onload = fileOnLoad;
head.appendChild(file[3]);
file[4] = document.createElement('link');
file[4].rel = "stylesheet";
file[4].href =
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap-theme.min.css";
file[4].onload = fileOnLoad;
head.appendChild(file[4]);
<div id="demo">0 file loaded</div>
Hope it helps!

If you can use jQuery, try $.getScript(). Docs here.

Related

Inject External JS into iFrame

I'm making a basic editing system. For this I have an iFrame on the page, and I am wanting to inject javascript into it. I've tried a few different methods and have come to a method which works (injecting inline script), ideally I don't want to do it this way, I want to inject an external script file. The current code I have is:
function addScript(src, myscript2) {
var iFrameHead = window.frames["main_iframe"].document.getElementsByTagName("head")[0];
var myscript = document.createElement('script');
myscript.type = 'text/javascript';
myscript.src = src;
iFrameHead.appendChild(myscript);
iFrameHead.innerHTML = iFrameHead.innerHTML + myscript2;
}
addScript("js/jquery-3.1.1.js", "<scr" + "ipt>function frame_hover(e) {e.preventDefault();console.log('hover enter ', $(e.target));}function frame_drop(e) {e.preventDefault();console.log('drop ', $(e.target));}console.log('iframe loaded and script loaded');</scr" + "ipt>");
This is working, the jQuery file is working, as is the inline script that's injected.
I want it to work like this:
tion injectJS() {
function addScript(src) {
var iFrameHead = window.frames["main_iframe"].document.getElementsByTagName("head")[0];
var myscript = document.createElement('script');
myscript.type = 'text/javascript';
myscript.src = src;
iFrameHead.appendChild(myscript);
}
addScript("js/jquery-3.1.1.js");
addScript("js/frame_script.js");
}
Although when doing it this way, I get the error: Uncaught ReferenceError: frame_hover is not defined at HTMLBodyElement.ondragenter
frame_hover is called from:
$('#main_iframe').contents().find('body')
.attr('ondragenter', 'frame_hover(event)');
the content of frame_script.js is:
function frame_hover(e) {
e.preventDefault();
console.log('hover enter ', $(e.target));
}
function frame_drop(e) {
e.preventDefault();
console.log('drop ', $(e.target));
}
console.log('iframe loaded and script loaded');
What really doesn't make sense to me with this, is that jQuery works injecting it this way, that's loaded exactly the same way as the external file frame_script.js is loaded, why is it not seeing the file?!

How to dynamically load and use/call a JavaScript file?

I need to dynamically load a JavaScript file and then access its content.
File test.js
test = function () {
var pub = {}
pub.defult_id = 1;
return pub;
}()
In this case it works:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="/test.js"></script>
</head>
<body>
<script type="text/javascript">
console.log(test.defult_id);
</script>
</body>
</html>
But I need to load it dynamically, and that way it does not work:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script type="text/javascript">
function loadjs(file) {
var script = document.createElement("script");
script.type = "application/javascript";
script.src = file;
document.body.appendChild(script);
}
loadjs('test.js');
console.log(test.defult_id);
</script>
</body>
</html>
Error: Uncaught ReferenceError: test is not defined(…)
You could do it like this:
function loadjs(file) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = file;
script.onload = function(){
alert("Script is ready!");
console.log(test.defult_id);
};
document.body.appendChild(script);
}
For more information read this article : https://www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking/
There is a great article which is worth reading for all the guys interesting in js script loading in www.html5rocks.com - Deep dive into the murky waters of script loading .
In that article after considering many possible solutions, the author concluded that adding js scripts to the end of body element is the best possible way to avoid blocking page rendering by js scripts thus speeding page loading time.
But, the author propose another good alternate solution for those people who are desperate to load and execute scripts asynchronously.
Considering you've four scripts named script1.js, script2.js, script3.js, script4.js then you can do it with applying async = false:
[
'script1.js',
'script2.js',
'script3.js',
'script4.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
Now, Spec says: Download together, execute in order as soon as all download.
Firefox < 3.6, Opera says: I have no idea what this “async” thing is, but it just so happens I execute scripts added via JS in the order they’re added.
Safari 5.0 says: I understand “async”, but don’t understand setting it to “false” with JS. I’ll execute your scripts as soon as they land, in whatever order.
IE < 10 says: No idea about “async”, but there is a workaround using “onreadystatechange”.
Everything else says: I’m your friend, we’re going to do this by the book.
Now, the full code with IE < 10 workaround:
var scripts = [
'script1.js',
'script2.js',
'script3.js',
'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];
// Watch scripts load in IE
function stateChange() {
// Execute as many scripts in order as we can
var pendingScript;
while (pendingScripts[0] && pendingScripts[0].readyState == 'loaded') {
pendingScript = pendingScripts.shift();
// avoid future loading events from this script (eg, if src changes)
pendingScript.onreadystatechange = null;
// can't just appendChild, old IE bug if element isn't closed
firstScript.parentNode.insertBefore(pendingScript, firstScript);
}
}
// loop through our script urls
while (src = scripts.shift()) {
if ('async' in firstScript) { // modern browsers
script = document.createElement('script');
script.async = false;
script.src = src;
document.head.appendChild(script);
}
else if (firstScript.readyState) { // IE<10
// create a script and add it to our todo pile
script = document.createElement('script');
pendingScripts.push(script);
// listen for state changes
script.onreadystatechange = stateChange;
// must set src AFTER adding onreadystatechange listener
// else we’ll miss the loaded event for cached scripts
script.src = src;
}
else { // fall back to defer
document.write('<script src="' + src + '" defer></'+'script>');
}
}
A few tricks and minification later, it’s 362 bytes
!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
"//other-domain.com/1.js",
"2.js"
])
NOTE: there was one similar solution but it doesn't check if the script is already loaded and loads the script each time. This one checks src property and doesn't add script tag if already loaded.
Loader function:
const loadCDN = src =>
new Promise((resolve, reject) => {
if (document.querySelector(`head > script[src="${src}"]`) !== null) return resolve()
const script = document.createElement("script")
script.src = src
script.async = true
document.head.appendChild(script)
script.onload = resolve
script.onerror = reject
})
Usage (async/await):
await loadCDN("https://.../script.js")
Usage (Promise):
loadCDN("https://.../script.js").then(res => {}).catch(err => {})
Dinamically loading JS files is asynchronous, so to ensure your script is loaded before calling some function inside, use the onload event in script:
function loadjs(file) {
var script = document.createElement("script");
script.type = "application/javascript";
script.onload=function(){
//at this tine the script is loaded
console.log("Script loaded!");
console.log(test);
}
script.src = file;
document.body.appendChild(script);
}

Loading simple JS code after page loads?

Is there a way to load the JavaScript code (without placing it in the very bottom line of HTML document) after the page has been loaded? Here's the script example:
<script type="text/javascript" src="http://www.flickr.com/badge_code_v2.gne?count=10&display=latest&size=t&layout=x&source=user&user=XXX"></script>
PS. the reason is that the above Flickr code loads slowly and hangs the site meanwhile.
you can try it
<script type="text/javascript">
// Use any event to append the code
$(document).ready(function()
{
var s = document.createElement("script");
s.type = "text/javascript";
s.src = "test.js";
// Use any selector
$("head").append(s);
});
</script>
off course this will require jquery library or else you can paste this code on body's onload method
well this is something useful
It's not possible to synchronously execute a script at a URL. Note further that synchronous anything, when networks (or even file systems!) are involved is a Bad Idea. Someone, sometime, somewhere will be on a slow system, or a slow network, or both, and suddenly you've just hung their UI in the process.
http://bugs.jquery.com/ticket/6557
alternatively you can try
http://yepnopejs.com/
Apply it to the body's onload method.
If you want to load a script after the entire page has finished loading (including all the elements) try this. (jQuery)
$(window).load(function() {
var js,
id = 'SCRIPT-ID',
ref = document.getElementsByTagName('script')[0];
if (document.getElementById(id)) {
return;
}
js = document.createElement('script');
js.id = id;
js.async = true;
js.src = "http://www.website.com/script.js";
ref.parentNode.insertBefore(js, ref);
});
If you want it to load once the DOM is rendered, just change $(window) to $(document)
var asyncLoad = function (url) {
if (typeof url !== 'string') {
return false;
}
var script = document.createElement('script'),
firstScript = document.getElementsByTagName('script')[0];
script.setAttribute('src', url);
firstScript.parentNode.insertBefore(script, firstScript);
return script;
};
// or, for scriptless pages, just add to the head;
var asyncLoad = function (url) {
if (typeof url !== 'string') {
return false;
}
var script = document.createElement('script'),
head = document.getElementsByTagName('head')[0];
script.setAttribute('src', url);
head.appendChild(script);
return script;
};
// apply:
document.addEventListener('DOMContentLoaded', function () {
asyncLoad('script_url');
});
If you require modular execution of your scripts you can use following library
http://headjs.com/

load scripts asynchronously

I am using several plugins, custom widgets and some other libraries from JQuery. as a result I have several .js and .css files. I need to create a loader for my site because it takes some time to load. it will be nice if I can display the loader before importing all the:
<script type="text/javascript" src="js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="js/myFunctions.js"></script>
<link type="text/css" href="css/main.css" rel="stylesheet" />
...
....
etc
I have found several tutorials that enable me to import a JavaScript library asynchronously. for example I can do something like:
(function () {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'js/jquery-ui-1.8.16.custom.min.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
for some reason when I do the same thing for all my files the pages does not work. I have been trying for so long to try to find where the problem is but I just cannot find it. First I thought that it was probably because some javascript functions depended on the others. but I loaded them in the right order using the time out function when one completed I proceeded with the next and the page still behaves weird. for example I am not able to click on links etc... animations still work though..
Anyways
Here is what I have been thinking... I believe browsers have a cache that's why it takes a long time to load the page for the first time and the next time it is quick. so what I am thinking of doing is replacing my index.html page with a page that loads all this files asynchronously. when ajax is done loading all those files redirect to the page that I plan on using. when using that page it should not take long to load since the files should alredy be included on the cache of the browser. on my index page (page where .js and .css file get loaded asynchronously) I don't care of getting errors. I will just be displaying a loader and redirecting the page when done...
Is this idea a good alternative? or should I keep trying on implementing the asynchronously methods?
EDIT
the way I load everything async is like:
importScripts();
function importScripts()
{
//import: jquery-ui-1.8.16.custom.min.js
getContent("js/jquery-1.6.2.min.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
//s.async = true;
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext1,1);
});
//import: jquery-ui-1.8.16.custom.min.js
function insertNext1()
{
getContent("js/jquery-ui-1.8.16.custom.min.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext2,1);
});
}
//import: jquery-ui-1.8.16.custom.css
function insertNext2()
{
getContent("css/custom-theme/jquery-ui-1.8.16.custom.css",function (code) {
var s = document.createElement('link');
s.type = 'text/css';
s.rel ="stylesheet";
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext3,1);
});
}
//import: main.css
function insertNext3()
{
getContent("css/main.css",function (code) {
var s = document.createElement('link');
s.type = 'text/css';
s.rel ="stylesheet";
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext4,1);
});
}
//import: jquery.imgpreload.min.js
function insertNext4()
{
getContent("js/farinspace/jquery.imgpreload.min.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext5,1);
});
}
//import: marquee.js
function insertNext5()
{
getContent("js/marquee.js",function (code) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext6,1);
});
}
//import: marquee.css
function insertNext6()
{
getContent("css/marquee.css",function (code) {
var s = document.createElement('link');
s.type = 'text/css';
s.rel ="stylesheet";
s.innerHTML=code;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
setTimeout(insertNext,1);
});
}
function insertNext()
{
setTimeout(pageReadyMan,10);
}
}
// get the content of url and pass that content to specified function
function getContent( url, callBackFunction )
{
// attempt to create the XMLHttpRequest and make the request
try
{
var asyncRequest; // variable to hold XMLHttpRequest object
asyncRequest = new XMLHttpRequest(); // create request object
// register event handler
asyncRequest.onreadystatechange = function(){
stateChange(asyncRequest, callBackFunction);
}
asyncRequest.open( 'GET', url, true ); // prepare the request
asyncRequest.send( null ); // send the request
} // end try
catch ( exception )
{
alert( 'Request failed.' );
} // end catch
} // end function getContent
// call function whith content when ready
function stateChange(asyncRequest, callBackFunction)
{
if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )
{
callBackFunction(asyncRequest.responseText);
} // end if
} // end function stateChange
and the weird part is that all the style's work plus all the javascript functions. the page is frozen for some reason though...
A couple solutions for async loading:
//this function will work cross-browser for loading scripts asynchronously
function loadScript(src, callback)
{
var s,
r,
t;
r = false;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = src;
s.onload = s.onreadystatechange = function() {
//console.log( this.readyState ); //uncomment this line to see which ready states are called.
if ( !r && (!this.readyState || this.readyState == 'complete') )
{
r = true;
callback();
}
};
t = document.getElementsByTagName('script')[0];
t.parentNode.insertBefore(s, t);
}
If you've already got jQuery on the page, just use:
$.getScript(url, successCallback)*
Additionally, it's possible that your scripts are being loaded/executed before the document is done loading, meaning that you'd need to wait for document.ready before events can be bound to the elements.
It's not possible to tell specifically what your issue is without seeing the code.
The simplest solution is to keep all of your scripts inline at the bottom of the page, that way they don't block the loading of HTML content while they execute. It also avoids the issue of having to asynchronously load each required script.
If you have a particularly fancy interaction that isn't always used that requires a larger script of some sort, it could be useful to avoid loading that particular script until it's needed (lazy loading).
* scripts loaded with $.getScript will likely not be cached
For anyone who can use modern features such as the Promise object, the loadScript function has become significantly simpler:
function loadScript(src) {
return new Promise(function (resolve, reject) {
var s;
s = document.createElement('script');
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
Be aware that this version no longer accepts a callback argument as the returned promise will handle callback. What previously would have been loadScript(src, callback) would now be loadScript(src).then(callback).
This has the added bonus of being able to detect and handle failures, for example one could call...
loadScript(cdnSource)
.catch(loadScript.bind(null, localSource))
.then(successCallback, failureCallback);
...and it would handle CDN outages gracefully.
HTML5's new 'async' attribute is supposed to do the trick. 'defer' is also supported in most browsers if you care about IE.
async - The HTML
<script async src="siteScript.js" onload="myInit()"></script>
defer - The HTML
<script defer src="siteScript.js" onload="myInit()"></script>
While analyzing the new adsense ad unit code I noticed the attribute and a search lead me here: http://davidwalsh.name/html5-async
I loaded the scripts asynchronously (html 5 has that feature) when all the scripts where done loading I redirected the page to index2.html where index2.html uses the same libraries. Because browsers have a cache once the page redirects to index2.html, index2.html loads in less than a second because it has all it needs to load the page. In my index.html page I also load the images that I plan on using so that the browser place those images on the cache. so my index.html looks like:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Project Management</title>
<!-- the purpose of this page is to load all the scripts on the browsers cache so that pages can load fast from now on -->
<script type="text/javascript">
function stylesheet(url) {
var s = document.createElement('link');
s.type = 'text/css';
s.async = true;
s.src = url;
var x = document.getElementsByTagName('head')[0];
x.appendChild(s);
}
function script(url) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = url;
var x = document.getElementsByTagName('head')[0];
x.appendChild(s);
}
//load scritps to the catche of browser
(function () {
stylesheet('css/custom-theme/jquery-ui-1.8.16.custom.css');
stylesheet('css/main.css');
stylesheet('css/marquee.css');
stylesheet('css/mainTable.css');
script('js/jquery-ui-1.8.16.custom.min.js');
script('js/jquery-1.6.2.min.js');
script('js/myFunctions.js');
script('js/farinspace/jquery.imgpreload.min.js');
script('js/marquee.js');
})();
</script>
<script type="text/javascript">
// once the page is loaded go to index2.html
window.onload = function () {
document.location = "index2.html";
}
</script>
</head>
<body>
<div id="cover" style="position:fixed; left:0px; top:0px; width:100%; height:100%; background-color:Black; z-index:100;">Loading</div>
<img src="images/home/background.png" />
<img src="images/home/3.png"/>
<img src="images/home/6.jpg"/>
<img src="images/home/4.png"/>
<img src="images/home/5.png"/>
<img src="images/home/8.jpg"/>
<img src="images/home/9.jpg"/>
<img src="images/logo.png"/>
<img src="images/logo.png"/>
<img src="images/theme/contentBorder.png"/>
</body>
</html>
another nice thing about this is that I may place a loader in the page and when the page is done loading the loader will go away and in a matte of milliseconds the new page will be running.
Example from google
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js?onload=onLoadCallback';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
Several notes:
s.async = true is not very correct for HTML5 doctype, correct is s.async = 'async' (actually using true is correct, thanks to amn who pointed it out in the comment just below)
Using timeouts to control the order is not very good and safe, and you also make the loading time much larger, to equal the sum of all timeouts!
Since there is a recent reason to load files asynchronously, but in order, I'd recommend a bit more functional-driven way over your example (remove console.log for production use :) ):
(function() {
var prot = ("https:"===document.location.protocol?"https://":"http://");
var scripts = [
"path/to/first.js",
"path/to/second.js",
"path/to/third.js"
];
function completed() { console.log('completed'); } // FIXME: remove logs
function checkStateAndCall(path, callback) {
var _success = false;
return function() {
if (!_success && (!this.readyState || (this.readyState == 'complete'))) {
_success = true;
console.log(path, 'is ready'); // FIXME: remove logs
callback();
}
};
}
function asyncLoadScripts(files) {
function loadNext() { // chain element
if (!files.length) completed();
var path = files.shift();
var scriptElm = document.createElement('script');
scriptElm.type = 'text/javascript';
scriptElm.async = true;
scriptElm.src = prot+path;
scriptElm.onload = scriptElm.onreadystatechange = \
checkStateAndCall(path, loadNext); // load next file in chain when
// this one will be ready
var headElm = document.head || document.getElementsByTagName('head')[0];
headElm.appendChild(scriptElm);
}
loadNext(); // start a chain
}
asyncLoadScripts(scripts);
})();
Thanks to HTML5, you can now declare the scripts that you want to load asynchronously by adding "async" in the tag:
<script async>...</script>
Note: The async attribute is only for external scripts (and should only be used if the src attribute is present).
Note: There are several ways an external script can be executed:
If async is present: The script is executed asynchronously with the rest of the page (the script will be executed while the page continues the parsing)
If async is not present and defer is present: The script is executed when the page has finished parsing
If neither async or defer is present: The script is fetched and executed immediately, before the browser continues parsing the page
See this: http://www.w3schools.com/tags/att_script_async.asp
I wrote a little post to help out with this, you can read more here https://timber.io/snippets/asynchronously-load-a-script-in-the-browser-with-javascript/, but I've attached the helper class below. It will automatically wait for a script to load and return a specified window attribute once it does.
export default class ScriptLoader {
constructor (options) {
const { src, global, protocol = document.location.protocol } = options
this.src = src
this.global = global
this.protocol = protocol
this.isLoaded = false
}
loadScript () {
return new Promise((resolve, reject) => {
// Create script element and set attributes
const script = document.createElement('script')
script.type = 'text/javascript'
script.async = true
script.src = `${this.protocol}//${this.src}`
// Append the script to the DOM
const el = document.getElementsByTagName('script')[0]
el.parentNode.insertBefore(script, el)
// Resolve the promise once the script is loaded
script.addEventListener('load', () => {
this.isLoaded = true
resolve(script)
})
// Catch any errors while loading the script
script.addEventListener('error', () => {
reject(new Error(`${this.src} failed to load.`))
})
})
}
load () {
return new Promise(async (resolve, reject) => {
if (!this.isLoaded) {
try {
await this.loadScript()
resolve(window[this.global])
} catch (e) {
reject(e)
}
} else {
resolve(window[this.global])
}
})
}
}
Usage is like this:
const loader = new Loader({
src: 'cdn.segment.com/analytics.js',
global: 'Segment',
})
// scriptToLoad will now be a reference to `window.Segment`
const scriptToLoad = await loader.load()
I would complete zzzzBov's answer with a check for the presence of callback and allow passing of arguments:
function loadScript(src, callback, args) {
var s, r, t;
r = false;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = src;
if (typeof(callback) === 'function') {
s.onload = s.onreadystatechange = function() {
if (!r && (!this.readyState || this.readyState === 'complete')) {
r = true;
callback.apply(args);
}
};
};
t = document.getElementsByTagName('script')[0];
t.parent.insertBefore(s, t);
}
Here is a great contemporary solution to the asynchronous script loading though it only address the js script with async false.
There is a great article written in www.html5rocks.com - Deep dive into the murky waters of script loading .
After considering many possible solutions, the author concluded that adding js scripts to the end of body element is the best possible way to avoid blocking page rendering by js scripts.
In the mean time, the author added another good alternate solution for those people who are desperate to load and execute scripts asynchronously.
Considering you've four scripts named script1.js, script2.js, script3.js, script4.js then you can do it with applying async = false:
[
'script1.js',
'script2.js',
'script3.js',
'script4.js'
].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
Now, Spec says: Download together, execute in order as soon as all download.
Firefox < 3.6, Opera says: I have no idea what this “async” thing is, but it just so happens I execute scripts added via JS in the order they’re added.
Safari 5.0 says: I understand “async”, but don’t understand setting it to “false” with JS. I’ll execute your scripts as soon as they land, in whatever order.
IE < 10 says: No idea about “async”, but there is a workaround using “onreadystatechange”.
Everything else says: I’m your friend, we’re going to do this by the book.
Now, the full code with IE < 10 workaround:
var scripts = [
'script1.js',
'script2.js',
'script3.js',
'script4.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];
// Watch scripts load in IE
function stateChange() {
// Execute as many scripts in order as we can
var pendingScript;
while (pendingScripts[0] && ( pendingScripts[0].readyState == 'loaded' || pendingScripts[0].readyState == 'complete' ) ) {
pendingScript = pendingScripts.shift();
// avoid future loading events from this script (eg, if src changes)
pendingScript.onreadystatechange = null;
// can't just appendChild, old IE bug if element isn't closed
firstScript.parentNode.insertBefore(pendingScript, firstScript);
}
}
// loop through our script urls
while (src = scripts.shift()) {
if ('async' in firstScript) { // modern browsers
script = document.createElement('script');
script.async = false;
script.src = src;
document.head.appendChild(script);
}
else if (firstScript.readyState) { // IE<10
// create a script and add it to our todo pile
script = document.createElement('script');
pendingScripts.push(script);
// listen for state changes
script.onreadystatechange = stateChange;
// must set src AFTER adding onreadystatechange listener
// else we’ll miss the loaded event for cached scripts
script.src = src;
}
else { // fall back to defer
document.write('<script src="' + src + '" defer></'+'script>');
}
}
for HTML5, you can use the 'prefetch'
<link rel="prefetch" href="/style.css" as="style" />
have a look at 'preload' for js.
<link rel="preload" href="used-later.js" as="script">
One reason why your scripts could be loading so slowly is if you were running all of your scripts while loading the page, like this:
callMyFunctions();
instead of:
$(window).load(function() {
callMyFunctions();
});
This second bit of script waits until the browser has completely loaded all of your Javascript code before it starts executing any of your scripts, making it appear to the user that the page has loaded faster.
If you're looking to enhance the user's experience by decreasing the loading time, I wouldn't go for the "loading screen" option. In my opinion that would be much more annoying than just having the page load more slowly.
I would suggest you take a look at Modernizr. Its a small light weight library that you can asynchronously load your javascript with features that allow you to check if the file is loaded and execute the script in the other you specify.
Here is an example of loading jquery:
Modernizr.load([
{
load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js',
complete: function () {
if ( !window.jQuery ) {
Modernizr.load('js/libs/jquery-1.6.1.min.js');
}
}
},
{
// This will wait for the fallback to load and
// execute if it needs to.
load: 'needs-jQuery.js'
}
]);
You might find this wiki article interesting : http://ajaxpatterns.org/On-Demand_Javascript
It explains how and when to use such technique.
Well, x.parentNode returns the HEAD element, so you are inserting the script just before the head tag. Maybe that's the problem.
Try x.parentNode.appendChild() instead.
Check out this https://github.com/stephen-lazarionok/async-resource-loader. It has an example that shows how to load JS, CSS and multiple files with one shot.
Have you considered using Fetch Injection? I rolled an open source library called fetch-inject to handle cases like these. Here's what your loader might look like using the lib:
fetcInject([
'js/jquery-1.6.2.min.js',
'js/marquee.js',
'css/marquee.css',
'css/custom-theme/jquery-ui-1.8.16.custom.css',
'css/main.css'
]).then(() => {
'js/jquery-ui-1.8.16.custom.min.js',
'js/farinspace/jquery.imgpreload.min.js'
})
For backwards compatibility leverage feature detection and fall-back to XHR Injection or Script DOM Elements, or simply inline the tags into the page using document.write.
Here is my custom solution to eliminate render-blocking JavaScript:
// put all your JS files here, in correct order
const libs = {
"jquery": "https://code.jquery.com/jquery-2.1.4.min.js",
"bxSlider": "https://cdnjs.cloudflare.com/ajax/libs/bxslider/4.2.5/jquery.bxslider.min.js",
"angular": "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.2/angular.min.js",
"ngAnimate": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.min.js"
}
const loadedLibs = {}
let counter = 0
const loadAsync = function(lib) {
var http = new XMLHttpRequest()
http.open("GET", libs[lib], true)
http.onload = () => {
loadedLibs[lib] = http.responseText
if (++counter == Object.keys(libs).length) startScripts()
}
http.send()
}
const startScripts = function() {
for (var lib in libs) eval(loadedLibs[lib])
console.log("allLoaded")
}
for (var lib in libs) loadAsync(lib)
In short, it loads all your scripts asynchronously, and then executes them consequently.
Github repo: https://github.com/mudroljub/js-async-loader
Here a little ES6 function if somebody wants to use it in React for example
import {uniqueId} from 'lodash' // optional
/**
* #param {String} file The path of the file you want to load.
* #param {Function} callback (optional) The function to call when the script loads.
* #param {String} id (optional) The unique id of the file you want to load.
*/
export const loadAsyncScript = (file, callback, id) => {
const d = document
if (!id) { id = uniqueId('async_script') } // optional
if (!d.getElementById(id)) {
const tag = 'script'
let newScript = d.createElement(tag)
let firstScript = d.getElementsByTagName(tag)[0]
newScript.id = id
newScript.async = true
newScript.src = file
if (callback) {
// IE support
newScript.onreadystatechange = () => {
if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') {
newScript.onreadystatechange = null
callback(file)
}
}
// Other (non-IE) browsers support
newScript.onload = () => {
callback(file)
}
}
firstScript.parentNode.insertBefore(newScript, firstScript)
} else {
console.error(`The script with id ${id} is already loaded`)
}
}
A concise answer, the explanations in the code
function loadScript(src) {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
//
// script.async = false; // uncomment this line and scripts will be executed in the document order, like 'defer' option
//
// script.defer = true; // uncomment this line when scripts need whole DOM and/or relative order execution is important
//
// the script starts loading as it's append to the document and dynamic script behave as “async” by default
// other scripts don’t wait for 'async' scripts, and 'async' scripts don’t wait for them
// scripts that loads first – runs first (“load-first” order)
document.body.append(script);
}
loadScript('js/example01.js');
loadScript('js/example02.js');
/*
the 'defer' attribute tells the browser not to wait for the script
the 'async' attribute make script to load in the background and run when ready
the 'async' and 'defer' attribute are only for external scripts
'defer' is used for scripts that need the whole DOM and/or their relative execution order is important
'async' is used for independent scripts, like counters or ads, when their relative execution order does not matter
More: https://javascript.info/script-async-defer
*/
You can use LABJS or RequreJS
Script loaders like LABJS, RequireJS will improve the speed and quality of your code.
const dynamicScriptLoading = async (src) =>
{
const response = await fetch(src);
const dataResponse = await response.text();
eval.apply(null, [dataResponse]);
}
I would suggest looking into minifying the files first and see if that gives you a big enough speed boost. If your host is slow, could try putting that static content on a CDN.

Load javascript async, then check DOM loaded before executing callback

Problem:
Load js files asynchronously, then check to see if the dom is loaded before the callback from loading the files is executed.
edit: We do not use jQuery; we use Prototype.
edit: added more comments to the code example.
I am trying to load all of my js files asynchronously so as to keep them from blocking the rest of the page. But when the scripts load and the callback is called, I need to know if the DOM has been loaded or not, so I know how to structure the callback. See below:
//load asynchronously
(function(){
var e = document.createElement('script');
e.type = "text/javascript";
e.async = true;
e.src = srcstr;
// a little magic to make the callback happen
if(navigator.userAgent.indexOf("Opera")){
e.text = "initPage();";
}else if(navigator.userAgent.indexOf("MSIE")){
e.onreadystatechange = initPage;
}else{
e.innerHTML = "initPage();";
}
// attach the file to the document
document.getElementsByTagName('head')[0].appendChild(e);
})();
initPageHelper = function(){
//requires DOM be loaded
}
initPage = function(){
if(domLoaded){ // if dom is already loaded, just call the function
initPageHelper();
}else{ //if dom is not loaded, attach the function to be run when it does load
document.observe("dom:loaded", initPageHelper);
}
}
The callback gets called properly due to some magic behind the scenes that you can learn about from this Google talk: http://www.youtube.com/watch?v=52gL93S3usU&feature=related
What's the easiest, cross-browser method for asking if the DOM has loaded already?
EDIT
Here's the full solution I went with.
I included prototype and the asynchronous script loader using the normal method. Life is just so much easier with prototype, so I'm willing to block for that script.
<script type="text/javascript" src="prototype/prototype.js"></script>
<script type="text/javascript" src="asyncLoader.js"></script>
And actually, in my code I minified the two files above and put them together into one file to minimize transfer time and http requests.
Then I define what I want to run when the DOM loads, and then call the function to load the other scripts.
<script type="text/javascript">
initPage = function(){
...
}
</script>
<script type="text/javascript">
loadScriptAsync("scriptaculous/scriptaculous.js", initPage);
loadScriptAsync("scriptaculous/effects.js", initPage);
loadScriptAsync("scriptaculous/controls.js", initPage);
...
loadScriptAsync("mypage.js", initPage);
</script>
Likewise, the requests above are actually compressed into one httpRequest using a minifier. They are left separate here for readability. There is a snippet at the bottom of this post showing what the code looks like with the minifier.
The code for asyncLoader.js is the following:
/**
* Allows you to load js files asynchronously, with a callback that can be
* called immediately after the script loads, OR after the script loads and
* after the DOM is loaded.
*
* Prototype.js must be loaded first.
*
* For best results, create a regular script tag that calls a minified, combined
* file that contains Prototype.js, and this file. Then all subsequent scripts
* should be loaded using this function.
*
*/
var onload_queue = [];
var dom_loaded = false;
function loadScriptAsync(src, callback, run_immediately) {
var script = document.createElement('script');
script.type = "text/javascript";
script.async = true;
script.src = src;
if("undefined" != typeof callback){
script.onload = function() {
if (dom_loaded || run_immediately)
callback();
else
onload_queue.push(callback);
// clean up for IE and Opera
script.onload = null;
script.onreadystatechange = null;
};
script.onreadystatechange = function() {
if (script.readyState == 'complete'){
if (dom_loaded || run_immediately)
callback();
else
onload_queue.push(callback);
// clean up for IE and Opera
script.onload = null;
script.onreadystatechange = null;
}else if(script.readyState == 'loaded'){
eval(script);
if (dom_loaded || run_immediately)
callback();
else
onload_queue.push(callback);
// clean up for IE and Opera
script.onload = null;
script.onreadystatechange = null;
}
};
}
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
}
document.observe("dom:loaded", function(){
dom_loaded = true;
var len = onload_queue.length;
for (var i = 0; i < len; i++) {
onload_queue[i]();
}
onload_queue = null;
});
I added the option to run a script immediately, if you have scripts that don't rely on the page DOM being fully loaded.
The minified requests actually look like:
<script type="text/javascript" src="/min/?b=javascript/lib&f=prototype/prototype.js,asyncLoader.js"></script>
<script type="text/javascript"> initPage = function(e){...}</script>
<script type="text/javascript">
srcstr = "/min/?f=<?=implode(',', $js_files)?>";
loadScriptAsync(srcstr, initPage);
</script>
They are using the plugin from: [http://code.google.com/p/minify/][1]
What you need is a simple queue of onload functions. Also please avoid browser sniffing as it is unstable and not future proof. For full source code see the [Demo]
var onload_queue = [];
var dom_loaded = false;
function loadScriptAsync(src, callback) {
var script = document.createElement('script');
script.type = "text/javascript";
script.async = true;
script.src = src;
script.onload = script.onreadystatechange = function() {
if (dom_loaded)
callback();
else
onload_queue.push(callback);
// clean up for IE and Opera
script.onload = null;
script.onreadystatechange = null;
};
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
}
function domLoaded() {
dom_loaded = true;
var len = onload_queue.length;
for (var i = 0; i < len; i++) {
onload_queue[i]();
}
onload_queue = null;
};
// Dean's dom:loaded code goes here
// do stuff
domLoaded();
Test usage
loadScriptAsync(
"http://code.jquery.com/jquery-1.4.4.js",
function() {
alert("script has been loaded");
}
);
You can always put your initial loader script at the bottom, right before the closing body tag.

Categories