How to wait for another JS to load to proceed operation? - javascript

I have an issue. One of my JS scripts needs Facebook SDK and Twitter widgets JS to load first. Facebook creates FB object, Twitter creates twttr object. Both of them create these objects AFTER my script fires, even though they're loaded from <head>.
I think solution is to periodically check if FB and twttr are defined, and then proceed with executing my script. But I have no idea how to do this.
I tried creating a loop
while (typeof FB === 'undefined' || typeof twttr === 'undefined' || typeof twttr.widgets === 'undefined') {
// run timeout for 100 ms with noop inside
}
But this clearly does not work as it keeps firing timeouts at a high speed and page hangs.
Please help me, I can't sleep because of this issue.

If the scripts are loaded in the normal, synchronous way, then just make sure that your <script> include appears after the library scripts in the document's <head>. If, on the other hand, those scripts are loading objects asynchronously (as seems to be the case), then create something like this:
function whenAvailable(name, callback) {
var interval = 10; // ms
window.setTimeout(function() {
if (window[name]) {
callback(window[name]);
} else {
whenAvailable(name, callback);
}
}, interval);
}
And use it like this:
whenAvailable("twttr", function(t) {
// do something
});
The function given in the second argument to whenAvailable will not execute until twttr is defined on the global window object. You can do the same thing for FB.
Important note: Those libraries probably also provide some built-in way to execute code after they have loaded. You should look for such hooks in their respective documentation.

Have you put your script to be executed on page load? (ie. body onload="do_this ();")
That should make your code execute once all external resources has been loaded.
Regarding the use of setTimeout
setTimeout will return immediately, if you'd like to wait for certain variable to be defined, use something as the below.
function when_external_loaded (callback) {
if (typeof FB === 'undefined' || typeof twtter === 'undefined') {
setTimeout (function () {
when_external_loaded (callback);
}, 100); // wait 100 ms
} else { callback (); }
}
...
when_external_loaded (function () {
alert (FB);
alert (twtter);
});

const checkIfLoaded = ('lib', cb) => {
const interval = setInterval(() => {
if (lib) {
typeof cb === 'function' && cb();
clearInterval(interval);
} else {
console.log('not yet');
}
}, 100);
}

If the Facebook scripts are being loaded asynchronously, Facebook has a supported way to execute code when it's library loads which should be much more efficient than polling for it. See this answer for an example: https://stackoverflow.com/a/5336483/816620.
If the Facebook scripts are being loaded synchronously, then you don't have to wait for them - they will load before any other scripts after them run.

Related

Best way to solve this "ReferenceError: Function is not defined"?

I have an AEM page with multiple components, these components have a .js file with a function that encloses all the client side logic. We then call that function inside the component's HTML:
<script>
window.bootstrap_component(function() {
init_component_name();
});
</script>
As stated before init_component_name is the name of a function that encompasses all the logic we need:
function init_component_name() {
//DO STUFF
}
The wrapper bootstrap_component fuction is defined in the shared head.html of all our pages as:
<script>
window.bootstrap_component = function (handler) {
if (typeof handler === 'function') {
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
handler();
} else {
document.addEventListener("DOMContentLoaded", function() {
handler();
});
}
}
}
</script>
This works okay and we don't have any actual issues but we recently started using Bugsnag for error monitoring and reporting and we're getting reports for almost every component saying ReferenceError on page so/and/so init_component_name() is not defined.
The reason I think this is happening is because the init_component_name() function is not declared within the script tag and because this, function(init_component_name) has been attached to the window object it is executing fine and you don't see any console errors.
If I am correct would modifying those scripts tags to be like this work?
<script>
window.bootstrap_component(function() {
window.init_component_name();
})
</script>
A colleague of mine wants to add a timeout to the init_component_name functions of like 1ms but it rubs me the wrong way. Is there a more sensible approach?
If I am correct would modifying those scripts tags to be like this work?
window.bootstrap_component(function() {
window.init_component_name();
})
Yes, but you then have the problem that you're writing multiple data to the global namespace, window, which isn't ideal. What if another third-party script decides to override it?
Ideally you'd have a single namespace and put everything on there, and write only that namespace to window.
window.something = {};
something.bootstrap_component = { //...
and
something.init_component_name = () => {
//DO STUFF
}
Or better still, use modules (though that will need some light code refactoring).
Don't do the timeout hack. It's really, really horrible; what if scripts take longer than a second to load for some reason? You're also forcing your UI to wait a second, often unnecessarily. This hack tends to feature where chronology and scope has not been thought out properly.

<script> dynamic script load issue - weird behaviour

I am having a weird issue that is rather difficult to recreate, but I will try to explain to the best of my abilities.
I have a setup.js which is a file that includes parameters for the scripts to be added to the document by the main script, like so:
{
script1: true,
script2: true,
script3: false
}
I have a main script file, lets call it main.js which basically checks the setup file and then for every "true" does the following:
var element = document.createElement('script');
element.src = "path/to/file.js"
document.getElementsByTagName('body').appendChild(element);
Now I've checked using console.log, and the main.js file is the one that has to execute all the way to the end until any of the files added to the body begin loading, however they are then loaded one-by-one and should become ready for usage.
Now here comes the tricky part
I need to know when ALL of them are finished loading in order to be able to use their functions in the rest of the main.js file, so I need some sort of "onload" handler. I've tried to attach a addEventListener('load', function(){}) to the last element that was added, and it worked.
However.....
Once I changed the event listener function to something such as:
addEventListener('load' function(){
script1Function(); //Which is defined in script1
script2Function(); //Which is defined in script2
}
It started throwing errors such as "script1Function is not defined".
I know that both scripts are loaded at this point, because I did a console.log("I have loaded") as the last line of each script, and those appeared in the console, but it is not possible to use the function definitions that were defined in these files.
What is even more.... if I instead do the following event handler:
addEventListener('load' function(){
alert("Last script loaded"); //This line was ADDED
script1Function(); //Which is defined in script1
script2Function(); //Which is defined in script2
}
It all suddenly works.
Adding "alert("whatever");" allows me to use the functions defined in the files.... I would assume it has something to do with "time needed for me to click "OK" on the alert dialog... but the entire thing doesn't make sense, because I know functions are defined by the time I am calling them.
/*
Example usage:
importAsync(["script1.js", "script2.js", "script3.js"]);
waitUntil(function() {
return ((typeof(script1Function) !== "undefined")
&& (typeof(script2Function) !== "undefined")
&& (typeof(script3Function) !== "undefined"));
});
*/
function waitUntil(testFunc) {
var start = Date.now();
var timeout = 3000;
do {
if((Date.now() - start) > timeout) {
console.log("waitUntil() timed out");
return false;
}
} while(!testFunc());
}
This is what I meant in the comments.
I direct your attention to the example above the function:
importAsync(["script1.js", "script2.js", "script3.js"]);
waitUntil(function() {
return ((typeof(script1Function) !== "undefined")
&& (typeof(script2Function) !== "undefined")
&& (typeof(script3Function) !== "undefined"));
});
importAsync is whatever function/method you have for loading the .js files - it's not relevant.
What is relevant, is waitUntil and the function you pass it. In the example above, I check whether functions from all three scripts exist (ergo: are not equal to undefined) and only then will execution of the script continue, due to the nature of the do..while loop. As I also mentioned in the comments, you're going to want a timeout on the do..while loop to prevent an infinite loop. You could add a parameter to waitUntil for the timeout as well if you'd like.
I hope this gives you a basic idea of what I meant in the comments and if you have any further questions, fire away!

Exporting Javascript Class Functions

I am trying to implement the module YSlow into my own project. A function I want to use looks like this:
YSLOW.registerRuleset = function (ruleset) {
YSLOW.controller.addRuleset(ruleset);
};
From what I can tell, YSlow is assigned here in the code at the beginning:
if (typeof YSLOW === 'undefined') {
YSLOW = {};
}
What I want to be able to do is export this class and be able to use this function. I would usually just put:
exports.sayHello = function() {
return "Hello"
};
However, I am unsure how to properly communicate with registerRuleset() when YSLOW is in the beginning. If I put sayHello() in yslow.js I can use it fine, but for any function with YSLOW at the beginning it does not work. I have tried putting exports before and after YSLOW in a function but have had no success.
So my question is, how can I get around this? And for that matter, what is YSLOW.function() even doing exactly and what is the importance of it?
Thanks!
I'm not familiar with YSLOW, hence I'm not clear with your query.
If you are trying to check whether YSLOW class objects are loaded, below code may help to identify whether loaded or not loaded.
this code will keep polling for the objects to be ready and available. This will poll for 20 milliseconds (you can increase to more).
Its a Kind of hack, you can keep polling for the objects whether it is loaded and ready to use.
function checkLoaded() {
if (YSLOW && YSLOW.controller && YSLOW.controller.addRuleset) {
return true;
}
return false;
}
/* Start: polling to check if YSLOW Objects are ready */
var timeout = Date.now() + 10000,
startTime = Date.now();
var intervalID = window.setInterval(function () {
if (checkLoaded()) {
console.log("GOT Ready at: ", Date.now() - startTime + "ms");
window.clearInterval(intervalID);
initLoad();//do your stuffs after loading here.
} else if (Date.now() > timeout) {
console.log("Not loaded, kill the polling.");
window.clearInterval(intervalID);
failedLoad(); //do your stuff for not loaded here
}
}, 20);

YepNope/Modernizr callbacks with global JavaScript variables and Internet Explorer

Can anyone explain why, in Internet Explorer, code sample 1 doesn't work while code sample 2 works?
Code 1 (non-functional)
Modernizr.load([
{
load: [
'../includes/css/foo.css',
'../includes/js/foo.js',
'../includes/js/bar.js'
],
complete: function() {
initBar();
}
}
]);
Code 2 (functional)
Modernizr.load([
{
load: [
'../includes/css/foo.css',
'../includes/js/foo.js',
'../includes/js/bar.js'
],
complete: function() {
window.initBar();
}
}
]);
bar.js
var initBar = function() {
// code here
};
It works fine in other browsers. I've tried moving the blocks to the head section as well as beneath the page. I've also tried wrapping the contents of the callback in $(document).ready(), but none have worked with code 1.
The error I am getting specifically is:
SCRIPT5009: « initBar » est indéfini
It is almost as if the callback is executed before the resources are finished loading, but if that was the case then why does code sample 2 work?
Also I will note that on refresh the page loads fine (most likely due to the fact that the resources are cached), but it also loads fine after clearing the cache. I have to restart my browser session after clearing the cache to reproduce the problem.
UPDATE:
This problem extends to more than just functions. Any global variable defined in a JS file that is loaded doesn't seem to be accessible directly. It also occurs if I load the CSS at the top of the page rather than with the other resources asynchronously. In fact I'm also noticing this problem with some jQuery plugins that are loaded in this manner.
UPDATE 2:
Here is the console.log() output as per debugging instructions below. I've changed bar to be an object instead of a function for the sake of illustrating this.
Internet Explorer:
HTML1300: Une navigation s’est produite.
Fichier : test18.php
before .load() called
before bar accessed
typeof bar = undefined
typeof window.bar = undefined
SCRIPT5009: « bar » est indéfini
Fichier : test18.js, ligne : 14, colonne : 13
before bar defined
So it appears that the complete function executes before bar is defined. I find it strange that window.bar is also undefined yet works...
Firefox
[02:10:46,448] "before .load() called"
[02:10:47,184] "before bar defined"
[02:10:47,184] "before bar accessed"
[02:10:47,184] "typeof bar = object"
[02:10:47,184] "typeof window.bar = object"
Chrome
before .load() called
before bar defined
before bar accessed
typeof bar = object
typeof window.bar = object
Both Firefox and Chrome appear to be loading and executing the resources in the correct order.
First off, you should know that .load() in modernizr comes from the yepnope library so that's where you find the detailed documentation for it.
Here are the possible things that could be different in different browsers that I can think of:
The exact timing of the loading of the scripts and thus the timing of when the complete() function gets called.
The caching in the browser (which can affect the load timing).
Because you are defining initBar by assigning it to a variable instead of a regular function initBar() definition, the function will not exist until that line of code executes whereas function initBar() will exist at script parse time.
Make sure you version 1.5 or higher of the yepnope loading library (I don't know what modernizr version that corresponds to. The yepnope doc for .load() says this: "In versions of yepnope prior to 1.5 this [when the complete function is called] could vary from time to time".
There are notes on this page that the yepnope library may not wait for .css files to load before calling the complete callback unless you have an add-in present. I don't know if that throws off the whole complete timing or what as I note that you do have .css files in your load list.
So, here's what I'd suggest to debug this:
1) Change your initBar definition to this:
function initBar() {
// code here
}
2) Make sure your initBar definition is in the proper scope and reachable from your other code. Look out for things like being inside another function (onload, document.ready, etc...) which might make it unreachable.
3) Insert some console.log() statements like this to do some timing debugging:
console.log("before .load() called");
Modernizr.load([
{
load: [
'../includes/css/foo.css',
'../includes/js/foo.js',
'../includes/js/bar.js'
],
complete: function() {
console.log("before initBar() called");
console.log("typeof initBar = " + typeof initBar);
console.log("typeof window.initBar = " + typeof window.initBar);
initBar();
console.log("after initBar() called");
}
}
]);
console.log("before initBar() defined");
function initBar() {
// code here
}
Then, see what order things come out in and what the typeof statements say. The idea here is to try to figure out if things are executing in the wrong order or if a scope is wrong.
4) Try loading the .css file separately so it won't be affecting the .js loading.
Here's a replacement script that can load multiple scripts dynamically to replace the modernizr buggy .load() code. This one loads them all in parallel. This works only for scripts files (though the same concept could be used for .css files.
function loadScriptsInParallel(scripts, completeCallback) {
var head = document.getElementsByTagName('head')[0];
var remaining = scripts.length, i, scriptTag;
function complete() {
// make sure it's not called again for this script
this.onreadystatechange = this.onload = function() {};
// decrement remaining count and check if all are done
--remaining;
if (remaining === 0) {
// all are done call the callback
completeCallback();
}
}
for (var i = 0; i < scripts.length; i++) {
scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scripts[i];
// most browsers
scriptTag.onload = complete;
// IE 6 & 7
scriptTag.onreadystatechange = function() {
if (this.readyState == 'complete') {
complete.apply(this, arguments);
}
}
head.appendChild(scriptTag);
}
}
Sample usage:
loadScriptsInParallel([
'../includes/js/foo.js',
'../includes/js/bar.js'
], function() {
// put code here for when all scripts are loaded
initBar();
});
Working demo: http://jsfiddle.net/jfriend00/qs44R/
If you need them loaded sequentially (one after the other because of dependencies between them), then you could use this:
function loadScriptsInSequence(scripts, completeCallback) {
var head = document.getElementsByTagName('head')[0];
var remaining = scripts.length, i = 0;
function loadNext() {
var scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scripts[i++];
// most browsers
scriptTag.onload = complete;
// IE 6 & 7
scriptTag.onreadystatechange = function() {
if (this.readyState == 'complete') {
complete.apply(this, arguments);
}
}
head.appendChild(scriptTag);
}
function complete() {
// make sure it's not called again for this script
this.onreadystatechange = this.onload = function() {};
// decrement remaining count and check if all are done
--remaining;
if (remaining === 0) {
// all are done call the callback
completeCallback();
} else {
loadNext();
}
}
loadNext();
}
Working demo: http://jsfiddle.net/jfriend00/9aVLW/

How can I check for/use jQuery in closure loaded before jQuery?

I have a js file that contains my closure, this file is loaded before jQuery, let's say it can't be moved. How can I pass in or check for jQuery with a view to use it in the closure?
This is what I've got so far:
(function MyClosure() {
var interval = setInterval(function() {
if (typeof jQuery !== 'undefined') {
doJqueryStuff();
clearInterval(interval);
}
}, 500);
function doJqueryStuff() {
// Some stuff with jQuery.
}
})();
It actually works, but is there a "better" way? I always think I'm doing something wrong whenever I use setInterval() for things like this, also the fact I am losing time in that 500ms.
You could wait and attach your execution to the window.onload event, assuming jQuery is loaded once the window is loaded...
window.onload = function() {
// do stuff with jQuery
};
Don't worry - while it does look hackish (at least to me and you) it isn't bad. Often times you need to wait until a complex object is initialized and you need to do the same thing. The best thing is to just ensure the order that your scripts load to solve any dependency issues - but as you requested let's assume the order can't be adjusted.
The only improvement I would suggest: adding an escape hatch to anonymous setInterval function. That way if jQuery never becomes available for some reason, the script can notify the user and stop checking.
var checkCount = 0;
var interval = setInterval(function() {
if (checkCount++ > 20) {
alert("jQuery could not be loaded - degrading user experience");
clearInterval(interval);
}
if (typeof jQuery !== 'undefined') {
doJqueryStuff();
clearInterval(interval);
}
}, 500);
Wait for the onload event on the script tag. In this case, the doJqueryStuff should be a global function.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" onload='doJqueryStuff()'></script>

Categories

Resources