Synchronous <script> statements? - javascript

I am using xmlhttprequest (and eval) to dynamically load scripts. Next I evaluate the script and look to see if there are any other scripts to load. Along the way if any of the scripts cause and exception, the error message reported indicates the line number associated with eval, not with the actual error in the script.
On another question it was suggested I use <script> instead to get better error messages. Unfortunately, <script> is asynchronous, and I won't be able to control the order of loading of scripts (I need an onload callback).
How do I implement synchronous behaviour in <script> commands
Some more info as to what I aim to achieve
Every script has a list of other scripts it loads, stored in a list, lets call it _toLoad
Lets say we have a script 'Main.js' with a load list like so
_toLoad = [['A.js'] , ['B.js'] , ['C.js'] , ['D.js' , 'E.js' , 'F.js']]
Which states that once loaded, the file 'A.js' must be loaded next. Once 'A.js' is loaded, 'B.js' must be loaded next. Once 'B.js' is loaded, 'C.js' must be loaded next. Once 'C.js' is loaded, 'D.js' ,'E.js' , and 'F.js' must be loaded, in any order.
I could use <script> to load 'Main.js', evaluate it's _toLoad list and start loading the other scripts, in the correct order. But what happens if 'A.js' has several scripts it also loads? I want those to load in the background, and not to delay 'B.js' from loading
What if 'A.js' has a load list like so:
_toLoad = [['A2.js'] , ['B2.js'] , ['C2.js'] , ['D2.js' , 'E2.js' , 'F2.js']]
Is I would have to go through and issue <script> statements for those. It seems like a depth first approach, when I want a form of breadth first.

One of the linked questions mentioned setting the innerHTML of the script tag element. I think this might do the trick
function load(toEval, callback){
__global_callback = callback;
var node = document.createElement('script');
node.innerHTML = toEval + '; __global_callback()';
document.getElementsByTagName('head')[0].appendChild(node);
}
load("console.log('before...');", function(){
console.log('...and after');
});

Related

Is it guaranteed that load event on a <script> tag is always fired immediately the script is executed?

Suppose there are two JavaScript files.
one.js
window.a=1;
two.js
window.a=2;
And the loader:
loader.html
<body></body>
<script>
const s1=document.createElement("script");
s1.src="one.js";
s1.addEventListener("load",()=>console.log("one.js",window.a));
const s2=document.createElement("script");
s2.src="two.js";
s2.addEventListener("load",()=>console.log("two.js",window.a));
document.body.appendChild(s1);
document.body.appendChild(s2);
</script>
Does it always produce as [Out-1] or [Out-2]? (Edited: It means neither "I want the code which always produces [Out-1]" nor "I want the code which always produces [Out-2]". Both [Out-1] and [Out-2] are acceptable.)
[Out-1](Run in order of s1-exec-> s1-onload -> s2-exec -> s2-onload. It is OK):
"one.js" 1
"two.js" 2
[Out-2](s2-exec-> s2-onload -> s1-exec -> s1-onload. It is OK, too):
"two.js" 2
"one.js" 1
What I am worrying is whether the browser may run in order of s1-exec -> s2-exec -> s1-onload -> s2-onload and produce as [Out-3]
[Out-3](Not OK):
"one.js" 2
"two.js" 2
I checked HTML5 specification
https://www.w3.org/TR/2008/WD-html5-20080610/tabular.html#script
ant it says:
If the load was successful
If the script element's Document is the active document in its browsing context, the user agent must execute the script:
....
Then, the user agent must fire a load event at the script element.
Does it guarantee that "Other script tags never interrupt the process between step 1 and step 2"?.
(edited) On the other words, I am wondering about whether the HTML specification requires the browser to run step1 thru step2 synchronously(The title "immediately" means this).
Per specs at least, yes it is guaranteed that the load event fires at the end of the "execute the script block" algorithm.
So, you can face [out-1] and [out-2], but not [out-3].
When you do append a <script> element in the document it will get loaded as an async script.
This means that the order of execution between script-1 and script-2 can't be guaranteed.
// scr1 will load an external script
const scr1 = document.createElement('script');
scr1.onload = e => console.log('1', window.$);
scr1.src = 'https://cdn.jsdelivr.net/gh/jquery/jquery#3.2/dist/jquery.js?' + Math.random();
// scr2 will load a dataURL script
const scr2 = document.createElement('script');
scr2.onload = e => console.log('2', window.$);
scr2.src = 'data:application/javascript,$="script 2"';
// even though scr1 is appended first
document.head.appendChild(scr1);
document.head.appendChild(scr2);
However when they do ask to fire the event at the end of the execute the script block algorithm, there is no place for any race condition, the event's callbacks will get executed synchronously.
(note: If they were asking to "queue a task to fire an event", like they do in other places, then the callbacks would have fired asynchronously, but still no race conditions since both tasks would get queued on the same task queue).

JavaScript async & defer: run a script asynchronously

As far as I am aware, in the script element, the async attribute allows the script to download asynchronously (similar to images), while the defer causes the script to wait until the end before executing.
Suppose I have two scripts to include:
<script src="library.js"></script>
<script src="process.js"></script>
I would want them both to proceed asynchronously, and I would want process.js to wait until the end to start processing.
Is there a way to get the library.js script to run asynchronously?
Note
I see there appears to be some discrepancy between different online resources as to the actual role of the async attribute.
MDN & WhatWG suggest that it means that the script should execute asynchronously. Other online resources suggest that it should load asynchronously, but still blocks rendering when it is executed.
I found Sequential script loading in JavaScript which might help you:
(function(){
//three JS files that need to be loaded one after the other
var libs = [
'https://code.jquery.com/jquery-3.1.1.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/underscore.string/3.3.4/underscore.string.js'
];
var injectLibFromStack = function(){
if(libs.length > 0){
//grab the next item on the stack
var nextLib = libs.shift();
var headTag = document.getElementsByTagName('head')[0];
//create a script tag with this library
var scriptTag = document.createElement('script');
scriptTag.src = nextLib;
//when successful, inject the next script
scriptTag.onload = function(e){
console.log("---> loaded: " + e.target.src);
injectLibFromStack();
};
//append the script tag to the <head></head>
headTag.appendChild(scriptTag);
console.log("injecting: " + nextLib);
}
else return;
}
//start script injection
injectLibFromStack();
})();
Both defer and async affect when a script is executed, not when a script is downloaded. I think the confusion comes from the fact that some documentation is a bit sloppy with terms, and the term loaded is unclear as to whether it refers to the fetching of the resource, or the execution of it.
To get library.js to run asyncronously without waiting for the document to load, use async attribute, and to get process.js to wait until document has been parsed, use defer:
<script src="library.js" async></script>
<script src="process.js" defer></script>
Note that library.js is not guaranteed to run before process.js, although it probably will.

Using require.js to load something before onload

I see this question was asked here
load javascript file before onload with requirejs
But the solution there doesn't fit my situation and so I'm wondering if there is a different solution. I can't build with deps and make my code come last because I'm making a library/utility, not an app.
I'm working on the WebGL-Inspector. It works by wrapping HTMLCanvasElement.prototype.getContext and if it sees a "webgl" it then does its thing wrapping the context and allowing you to inspect it.
The WebGL-Inspector works in 3 modes
As a browser extension
As a loader + large compiled script
As a loader + original source (many many scripts)
To use it in modes #2 or #3 above you just insert
<script src="core/embed.js"></script>
Somewhere in the top of your HTML. Because it loads synchronously~ish it will have wrapped HTMLCanvasElement.prototype.getContext before whatever scripts come after it.
These last 2 modes are mostly for debugging/development of the WebGL-Inspector itself. Especially mode #3 because we can edit the inspector's code and refresh the page immediately to see the result, no build step.
I'm in the process of switching to using AMD for all of the WebGL-Inspector. We're using this because we can use webpack to make #2 but still follow the same dev workflow allowing us it use mode #3 above just change the script tag to
<script src="core/require.js" data-main="core/embed.js"></script>
The problem is this no longer works because whatever other code unrelated to the WebGL-Inspector itself runs before core/embed.js has loaded and so calls someCanvas.getContext before we've had a chance to wrap it.
My current solution is sadly to hack in a delay of 1.5 seconds on whatever demo we're using
<script src="core/require.js" data-main="core/embed.js"></script>
<script>
// wait 1.5 seconds for embed.js to load and pray :(
window.onload = setTimeout(reallyRunWebGLCode, 1500);
...
</script>
The previous non-AMD loader doesn't have this async issue. Somehow it manages to load 60 or so .js files before window.onload fires. Is there a way to get require.js to do the same? Or maybe I need to write my own loader?
To put it another way, the issue is as it is now the user adds a single <script> line and makes no other changes to their code and it works. When they're done they remove the single script line.
Switching to AMD/require.js the user is now required to add a single script and re-write code. In my tests went from
window.onload = myapp;
to
require.config({ baseUrl: "/core" });
require("./embed", myapp);
It's minor but now the app is broken when you remove the <script> tag and has to be put back as it was. Requiring those changes is what I'm trying to avoid if possible. In fact with the original style you don't even have to remove the script tag, just run your app in an environment where the script doesn't exist, it will fail to load and your app will run as normal where as with the require method it will fail if the script doesn't exist.
I can require even more code
if (typeof require === 'function' && typeof define === 'function' and define.amd) {
require.config({ baseUrl: "/core" });
require("./embed", myapp);
} else {
window.onload = myapp;
}
But that's even uglier. We went from adding a single script to requiring modifying your app.
Actually it gets even worse in the app I'm currently testing with. It uses a different loader for itself. When I run require version of the code above it fails because in this case require runs myapp before window.onload (strange). I end up having to do this
var tryRunCount = 0;
function tryRunApp() {
++tryRunCount;
// check that tryRunApp has been called twice so we know
// both onload and require have returned
if (tryRunCount === 2) {
myApp();
}
}
window.onload = tryRunApp;
require.config({ baseUrl: "/core" });
require("./embed", tryRunApp);
That's way way more changes than I want to require users to make.

Load js Asynchronously But Execute Synchronously

The Scenario is that I have lots of js files. Different files are called depending on different platforms.
So I have a question as I want to load files Asynchronously. But the Execution of these files should be done synchronously. Currently I am doing this function call
function loadScriptJs(src) {
Console.log(' Call to file '+src);
var file = document.createElement('script'),
head = document.getElementsByTagName('head')[0];
file.type = 'text/javascript';
file.setAttribute("defer","defer");
file.setAttribute("src",src);
head.appendChild(file);
file.onload = function() {
console.log(src + " is loaded.");
};
}
Now I have a array which has a list of js files being called. I loop through the array and call this function for each src.
I have used document.ready function with in each of the js file. Here I have checked the dependencies of the files and made the sequence optimized. ie The dependent files are loaded later (come later in the array) and the more Parent like files come earlier.
Example:- If I have files A1,B2,C3,D4,E5 ..... like wise. My array is ['js/A1.js','js/B2.js','js/C3.js'...]. When I run this through the loop.
I get the consoles of 'Call to file [filename]' is the same sequence as in the array but consoles of '[filename] is loaded' are not coming in the intended sequence.
Yes I cannot control the loading as its over the internet. But I want to the execution to be synchronous. ie Begin all file execution when the last file completes loading.
Can anyone provide me any advice, suggestion or idea for it.
Dynamically inserted scripts may not adhere to the defer attribute. script tags have an async attribute. By default it's true. Explicitly set it to false.
file.setAttribute("async", "false");
or in plain HTML
<script type="text/javascript" async=false src="A1.js"></script>
You might also need to remove the document.ready method in each script and start the script's logic at the top level, appending each JS script to the bottom of the body tag instead of the header. If multiple scripts register for the ready event before it's fired you do not know what order they will be called in.

What's the best way to execute something only when all my JavaScript is loaded using jQuery?

I'm developing a web page using jQuery, and I want it to execute some code only after ALL my JavaScript files are fully loaded. The head section of my HTML has the following script.
<script src="supervisor/lib/jquery-1.6.min.js" type="text/javascript"></script>
Inside jQuery file, I inserted the following code:
$.getScript('functions.js', function () {
// code here
});
The file functions.js has the following code:
$.getScript('mask.js');
$.getScript('validations.js');
$.getScript('lotsofscripts.js');
// and more...
I want the code here in the first $.getScript() to execute only after ALL the other JS are loaded, but this is not ocurring. What's the best way to achieve this?
PS: I'm using lots of $.getScript() because I find easier to separate them, but I want them to be inserted inside the same file.
You could always just increment a counter. That way your getScript calls remain asynchronous, as the last thing you want to do is change that. And frankly, any packaged solution you find to loading the scripts in parallel and then executing some function afterward will probably just be a shinier version of this:
var counter = 0;
var filesToLoad = ["mask.js", "validations.js", "lotsofscripts.js"];
var filesCount = filesToLoad.length;
// Increment counter each time a getScript completes
function incrementCounter() {
if (++counter === filesCount) {
// This code will execute after everything loads
}
}
// Iterate through the files and run getScript on them,
// with incrementCounter as the callback
for (var i = 0; i < filesCount; i++) {
$.getScript(filesToLoad[i], incrementCounter);
}
Here's a jsFiddle example.
Assuming you know the name of a function defined in a js script that needs to be tested for whether or not it has loaded...
I use Underscore.js isFunction() to figure this out ( http://documentcloud.github.com/underscore/#isFunction )
Example, if script.js contains a function myScriptFunction(), you can write a function that checks:
if (_.isFunction(myScriptFunction)) {
// script.js is loaded
// OK to move on to the next step
} else {
// script.js is not loaded
// check again later
}
I have tried binding to events to figure out if a js script file is loaded, but it doesn't seem to work across all the browsers.
I would suggest HeadJS to load your JS files. You can execute specific code upon completion of specific files or groups of files. Take a look, it's a great little project.
You might want to use jQuery.ajax() instead. You can set the async option to false (it's true by default when you use getScript

Categories

Resources