Is an async javascript evaluated entirely before relinquishing event loop control? - javascript

I load the Google Maps JS asynchronously:
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = '//maps.googleapis.com/maps/api/js?....';
document.body.appendChild(script);
One of my scripts depends on the google.maps object it provides, so I check if google.maps is defined, and if not, I try again in 200ms:
initializeGoogleMaps: function() {
if (typeof google === 'undefined') {
window.setTimeout(this.initializeGoogleMaps.bind(this), 200);
} else {
var autocomplete = new google.maps.places.Autocomplete(...)
}
}
However when I look at the Google Maps script that's getting loaded, it begins like this:
window.google = window.google || {};
google.maps = google.maps || {};
(function() {
// then a lot more
Here's my question: Do I have a race condition here? Is it possible for a setTimeout to fire while an async script is being evaluated? Visually:
// This is the googleapis.com 3rd party script. It has been downloaded
// by the browser, and is now in the process of being evaluated.
window.google = window.google || {}; // google is now defined
google.maps = google.maps || {}; // google.maps is now defined
// ==== Can my setTimeout fire here? =====
// My `if (typeof google === 'undefined')` would be
// FALSE here, even though this script has not finished loading!
// Or, due to the single thread, will the browser finish evaluating
// this script before allowing queued up setTimeouts to fire?
// =======================================
(function() {
// then a lot more
I know JS is in some sense single threaded, but I wasn't sure how that played with async scripts. When an async script starts to get evaluated, does it finish loading the whole file before control is relinquished back to the event loop for other async scripts and setTimeout callbacks?

Do I have a race condition here?
Nope. JavaScript is "single-threaded", which means that every script and every callback is completely evaluated ("run to completion") before the next on (in the event loop) will start.
Is it possible for a setTimeout to fire while an async script is being evaluated?
Technically, the timeout can expire while the script is still being evaluated (quite likely when the script takes a long time to execute), but when it expires that doesn't mean the callback is immediately executed, it is just scheduled to be run as soon as possible (after the current event loop turn finishes).

The behavior you're asking about will likely be inconsistent among different browsers and should not be relied on. If the script executions happen on different threads with a shared state, what you're afraid of is possible. This however is not likely given the asynchronous design choices in Javascript. If the scripts are executed on the event loop, they are executed sequentially in full.
I would nevertheless go with using asynchronous patterns like Promises now native to modern browsers or simply use the Google Maps <script> element's onload event like so:
var maps_script = document.createElement('script');
maps_script.type = 'text/javascript';
maps_script.async = true;
maps_script.onload = initializeGoogleMaps;
maps_script.src = '//maps.googleapis.com/maps/api/js?...';
setTimeout is not a very effective way to manage causally-related events, especially when event handlers, callbacks and promises are available expressly for that purpose.

Related

Writing Javascript library: Asynch load timing issue

In the JS library I am writing, I have this loadScript func:
function loadScript(src, callback) {
var script = document.createElement('script');
script.src = src;
// script.type = "text/javascript";
// script.async = false;
if (typeof callback !== 'undefined') {
script.onload = function () {
callback();
};
}
document.head.appendChild(script);
}
Inside the main js file, I use it to load the dependencies dynamically after the main js file is loaded, initiate the JSLib object in its callback.
loadScript(baseUrl + '/dependencies/you-load-me-long-time.js', function() {
window.JSLib = true;
}
Then I have a web page that calls this library.
var jsLib = new JSLib({...});
The problem that I encounter is that - in the web page that loads this JS library, the browser complains that the JSLib is not defined because the dependency file you-load-me-long-time.js has not finished loading yet when the script in the web page is executed.
A work-around that seems to be working for now is that, in the web page, I wrap the initiation code in a $(window).load(function() {}); call.
Is there any way that I can overcome this timing issue? ex: "blocking" the loading of the rest of the web page until JSLib is loaded (doesn't sound like a good idea anyway), etc...
There are only two ways to create blocking dynamic script loaded via JS and both are fairly undesirable.
If the document is still being parsed, you can use document.write() to insert a <script> tag at the current document location. That will then be parsed and loaded synchronously.
If the script resource is on the same origin as the document, you can fetch the script with a synchronous Ajax call and then eval the script.
Since neither of these is particularly desirable, the usual work-around is to surface a callback all the way back to the caller of the script so that they can participate in when the async operation is done and put their code in that async callback.
var jsLib = new JSLib({...}, function() {
// put code here that uses the jsLib because now it is loaded
});
For this messy reason, it is usually not a good practice to make the completion of a constructor be an async operation. It significantly complicates the use of the object.
More common would be to let the constructor just create the shell of the object and then require a .load(fn) method call to actually load it. This will likely lessen the chance of callers misuing the library.
var jsLib = new JSLib({....});
jsLib.load(function(err) {
if (err) {
// error loading the library
} else {
// library is loaded now and all functionality can be used
}
});
FYI, your idea to use $(window).load() is not a good idea. That method may accidentally work just because it delays the timing enough until your script happens to be loaded, but the window load event does not specifically wait until dynamically loaded scripts have been loaded so it is not a reliable way to wait for your script.

Unload JS loaded via load() to avoid duplicates?

I'm building a dynamic website that loads all pages inside a "body" div via jquery's load(). The problem is I have a script looped with setInterval inside the loaded PHP page, the reason being I want the script loaded only when that page is displayed. Now I discovered that the scripts keep running even after "leaving" the page (loading something else inside the div without refresh) and if I keep leaving / returning the loops stack up flooding my server with GET requests (from the javascript).
What's a good way to unload all JS once you leave the page? I could do a simple dummy var to not load scripts twice, but I would like to stop the loop after leaving the page because it's causing useless traffic and spouting console errors as elements it's supposed to fill are no longer there.
Sorry if this has already been asked, but it's pretty hard to come up with keywords for this.
1) why don't you try with clearInterval?
2) if you have a general (main) function a( ) { ... } doing something you can just override it with function a() { }; doing nothing
3) if you null the references to something it will be garbage collected
no code provided, so no more I can do to help you
This really sounds like you need to reevaluate your design. Either you need to drop ajax, or you need to not have collisions in you method names.
You can review this link: http://www.javascriptkit.com/javatutors/loadjavascriptcss2.shtml
Which gives information on how to remove the javascript from the DOM. However, modern browsers will leave the code in memory on the browser.
Since you are not dealing with real page loads/unloads I would build a system that simulates an unload event.
var myUnload = (function () {
var queue = [],
myUnload = function () {
queue.forEach(function (unloadFunc) {
undloadFunc();
});
queue = [];
};
myUnload.add = function (unloadFunc) {
queue.push(unloadFunc);
};
return myUnload;
}());
The code that loads the new pages should just run myUnload() before it loads the new page in.
function loadPage(url) {
myUnload();
$('#page').load(url);
}
Any code that is loaded by a page can call myUnload.add() to register a cleanup function that should be run when a new page is loaded.
// some .js file that is loaded by a page
(function () {
var doSomething = function () {
// do something here
},
timer = setInterval(doSomething, 1000);
// register our cleanup callback with unload event system
myUnload.add(function () {
// since all of this code is isolated in an IIFE,
// clearing the timer will remove the last reference to
// doSomething and it will automatically be GCed
// This callback, the timer var and the enclosing IIFE
// will be GCed too when myUnload sets queue back to an empty array.
clearInterval(timer);
});
}());

execute function only once

I'm writing a greasemonkey script and want to call the start function only once after the page loads - it's for facebook. First I started with following code:
function start(){
alert("hello");
}
start();
The start() function was executed more then once. So I changed the code to following:
jQuery.noConflict();
window.stoop = 0;
jQuery(document).ready(function(){
if(window.stoop == 0){
start();
}
window.stoop = 55;
//or window.stoop++;
});
function start(){
alert("hello");
}
The problem is that the window.stoop value won't change.
I tried with
var stoop = 0;
stoop++;
and
var obj = {};
obj.stoop = 0;
obj.stoop++;
too, but these ways didn't work neither.
What am I doing wrong? I'm in Europe right now -it's night, so I will answer your questions later.
The issue is that your whole Greasemonkey script is executing more than once. Greasemonkey will run on iframes, just as though they were the main page -- if the iframe matches the #include, #exclude, and #match directives of your script.
There is no point in trying to track state like that, the function will only run once per script execution, unless you deliberately call it more than once (Which is not shown in the question). And scripts can't normally share information between execution instances (nor is that needed here).
Also, there is no need to use jQuery(document).ready() because, unless you are injecting the script into the target page, Greasemonkey fires at document.ready by default.
To solve the multiple run issue:
Tune your #include, #exclude, and #match directives to eliminate as many undesired iframes as you reasonably can.
Add code like this near the top of your script:
if (window.top != window.self) //-- Don't run on frames or iframes.
return;

What is an efficient way of waiting for a dynamically loaded Javascript file to finish loading?

I'm loading a Javascript file that has a function in it that I'd like to call. This is not possible, since between 'initiating' the load, and actually calling the function, the JS isn't loaded yet. Of course, I could do something like setTimeout('functionCall();',5000);, but I don't think this is a very efficient method, and it seems really unstable to me. That's why I was wondering whether there was a better way to do it.
Here's the code I'm using. The function in question is called controllerStart. If I comment out the last line here, and type it into a Javascript terminal (like on Chrome developer tools), everything works.
function loadController(name){
clearAll();
scriptContainer.innerHTML = '';
var scriptElement = document.createElement('script');
scriptElement.setAttribute('src','controllers/' + name + '.js');
scriptElement.setAttribute('type','text/javascript');
scriptContainer.appendChild(scriptElement);
controllerStart();// <-- Doesn't work from here, but works on a terminal
}
Thanks for taking a look!
call the function after you reference the Javascript.
JS loading is SYNCHRONOUS.
So.....
---- js library call -----
----- call the function here -----
Profit!
edit: specifically :
function loadController(name){
clearAll();
scriptContainer.innerHTML = '';
var scriptElement = document.createElement('script');
scriptElement.setAttribute('src','controllers/' + name + '.js');
scriptElement.setAttribute('type','text/javascript');
scriptContainer.appendChild(scriptElement);
scriptElement.onload = new function(){controllerStart();};
//something like that.
}
edit 2: my bad - use "onload" not "load" - too much jquery on the brain
more about "onload" here: http://www.onlinetools.org/articles/unobtrusivejavascript/chapter4.html
There is a good post from Nicholas C. Zackas about it which you can read here, that includes just the code you want to use.
Basically it's just a function that includes both a URL of a JS file to load, and a callback to execute when it's been loaded. It then creates the <script> tag, attaches the callback to execute after it's loaded (via the onreadystatechange or the onload event) and inserts it into the DOM.
Then, all you need to do is call it like:
loadScript('controllers/' + name + '.js', controllerStart);
From the sound of it you're loading your JavaScript asynchronously. In more recent browsers there's an onload event on the tag which fires when it's finished loading. But if you need cross-browser support, the only way to do it is to either:
(a) if it's loaded from the same server as your page, load your javascript using AJAX and then execute it with eval() when it's loaded (instead of using <script> tags),
(b) if you control the file add an event trigger to the end of it which you then listen for in your main JavaScript file. JQuery's bind() and trigger() functions are handy for that.
(c) if it's loaded from somewhere else and you don't control it (but do have permission to redistribute it), relocate it to your server and follow (a).
You also could do away with asynchronous loading and just put a stream of <script> tags in your header, the old fashioned way. That guarantees that each script won't be run until after the previous script has finished.

asynchronous javascript prepare function

To speed up my application I want to prepare some data before DOM is ready and then use this data when DOM is ready.
Here's how it might be:
var data = function prepareData(){
...
}();
$(document).ready(function() {
// use data to build page
}
How to prepare the data for later use?
Thanks
You need should use parentheses around the function expression for clarity (and because in a similar situation where you're defining and calling a function but not using the return value, it would be a syntax error without them). Also, when you use a function expression, you want to not give it a name. So:
var data = (function(){
...
})();
or use a function declaration instead:
var data = processData();
function processData() {
...
}
(Why not use a name with a function expression? Because of bugs in various implementations, especially Internet Explorer prior to IE9, which will create two completely unrelated functions.)
However, it's not clear to me what you're trying to achieve. When the browser reaches the script element, it hands off to the JavaScript interpreter and waits for it to finish before continuing building the DOM (because your script might use document.write to add to the HTML token stream). You can use the async or defer attributes to promise the browser you're not going to use document.write, on browsers that support them, but...
Update: Below you've said:
because prepareData is long time function and I assumed that browser can execute this while it's building DOM tree. Unfortunately '$(document).ready' fires before prepareData is finished. The question is how to teach '$(document).ready' to wait for ready data
The only way the ready handler can possibly trigger while processData is running is if processData is using asynchronous ajax (or a couple of edge conditions around alert, confirm, and the like, but I assume you're not doing that). And if it were, you couldn't be returning the result as a return value from the function (though you could return an object that you continued to update as the result of ajax callbacks). Otherwise, it's impossible: JavaScript on browsers is single-threaded, the ready handler will queue waiting for the interpreter to finish its previous task (processData).
If processData isn't doing anything asynchronous, I suspect whatever the symptom is that you're seeing making you think the ready handler is firing during processData has a different cause.
But in the case of asynchronous stuff, three options:
If you're not in control of the ready handlers you want to hold up, you might look at jQuery's holdReady feature. Call $.holdReady(true); to hold up the event, and use $.holdReady(false); to stop holding it up.
It's simple enough to reschedule the ready handler. Here's how I'd do it (note that I've wrapped everything in a scoping function so these things aren't globals):
(function() {
var data = processData();
$(onPageReady);
function processData() {
}
function onPageReady() {
if (!data.ready) {
// Wait for it to be ready
setTimeout(onPageReady, 0); // 0 = As soon as possible, you may want a
// longer delay depending on what `processData`
// is waiting for
return;
}
}
})();
Note that I happily use data in the onPageReady function, because I know that it's there; that function will not run until processData has returned. But I'm assuming processData is returning an object that is slowly being filled in via ajax calls, so I've used a ready flag on the object that will get set when all the data is ready.
If you can change processData, there's a better solution: Have processData trigger the ready handler when it's done. Here's the code for when processData is done with what it needs to do:
$(onPageReady);
That works because if the DOM isn't ready yet, that just schedules the call. If the DOM is already ready, jQuery will call your function immediately. This prevents the messy looping above.

Categories