Modernizr.load callback executing before loaded script - javascript

At a certain point on my javascript I have the following (using Modernizr 2.6.2):
Modernizr.load([{
load: '/path/file.js',
complete: function () {
//do stuff
}
}]);
It works great most of the time, except on IE8, about 1 time out of 3 it executes the callback first then the script being loaded. I am adding a break point on the callback and another inside file.js. Both scripts are being executed, just swapping the order at times.
I tried to isolate and reproduce the error, but I couldn't. It works fine every time when I have only this code on the page (using the exact same Modernizr file). For that reason I believe something else on my project could be interfering, but I have no clue on what to look next along the 10000 (really!) lines of js. Has anyone experienced anything similar or has any idea on what could cause this sort of behaviour?
I am not looking for a workaround for this specific case. This is one of many instances where I really would like to keep as is.
EDIT 1 - NEW INFO:
Using the network panel, I can see that two requests are being done (which is expected), but the first request always returns blank. The second request brings up the correct content. That works fine when the callback is executed after both requests are complete, but sometimes the callback is executed between the first (empty) and second request. That is when it crashes!
EDIT 2 - NEW INFO: Ok seems like I can reproduce it now. To be 100% sure on my assumptions I would have to know the javascript engine in deep, which I don't, so I could be dead wrong, but here is what I found:
Request file1.js and file2.js. If file2 finishes loading while the engine is busy executing file1's callback, file2 execution goes somewhere at the end of the queue, which means after file2's callback.

Try waiting for all of the files to load before firing the callbacks. Expand on the pattern below to suit your needs.
Modernizr.load([{
load: '/path/file.js',
complete: function () {
filesLoaded++;
checkStatus();
}
}]);
function checkStatus(){
if(filesLoaded == 2){
//do stuff
}else{
setTimeout(checkStatus, 100);
}
}

After doing some research on the subject, I think this issue may be more common than it seems at first, but has been overlooked because it is context dependent, and has different ways of being described:
Modernizr load [A, B] seems to execute B before A, how is this possible?
YepNopeJS: callback triggered before tiny JS file is fully loaded
https://github.com/SlexAxton/yepnope.js/issues/195
After reading this nice article (referenced on yepnope page), about the inconsistencies experienced on IE, I came up with a fix, which has been sent to yepnope maintainers via github:
http://github.com/SlexAxton/yepnope.js/pull/196
I don't know whether it is going to be accepted or not, but it solved my problem.
As editing a library may not always be a good idea, I would suggest the following workaround:
//instead of
complete:function(){
doStuff()
}
//try
complete:function(){
setTimeout(doStuff, 0);
}

Related

SetTimeout callback not executed on page refresh

I have a very weird problem in my webpage. On page load, my code calls some setTimeout() functions. They work fine when the page loads the first time. By 'work find' I mean the callback functions execute after a period of time as expected. BUT if I refresh the page, the setTimeout() function appears to execute BUT the callback functions do not.
I have verified this by simply console logging like so:
....
console.log('Step 1');
setTimeout(function() {
console.log('Executed');
...
}, 10);
....
On first page load the output is:
Step 1
Executed
On refreshing that page the output is:
Step 1
Can you provide any advice why this may happen? As murphy's law has it if I create a simple HTML and JS example of the problem it does not exhibit itself. But in my web application it does. I believe I have tracked down the issue to the setTimeout being executed but the callback not executing though. I just don't know why this problem would occur or how to fix it. Getting someone to point me in the right direction to explore solutions would be extremely helpful.
Relevant information:
The webpage uses no JS frameworks (React, etc.). Its uses JQuery, JQuery UI, and a rather large JQuery plugin I've made.
The bug can be reproduced on Chrome, Firefox and Edge (Safari not tested).

$(window).load fires too quickly in IE with asynchronous js libs

I have some serious problem with getting asynchronously some js libs and executing them in $(window).load in IE
all works in other browsers of course
so the problem is, that I'm doing something like
<script type="text/javascript">
var scr1 = document.createElement('script');
scr1.type = 'text/javascript';
scr1.src = 'some_lib.js';
$('BODY').prepend(scr1);
</script>
Just before </body> and use $(window).load method in html above it to operate on some plugins in some_lib.js, but it all happens to fast in IE, probable because of that asynchronous lib including, and I get an error, that method is not available for the element.
Is there any chance of maybe modyfying $(window).load method so I still could use it in the same way for every browser ?
Any code that you have in the window.load() call must be placed in a function (called onLoad in this example).
Every time you have a script that you dynamically load, increment a counter. Also include something to decrement that counter...
src1.onload = function() { counter--; onLoad(); }
Then in 'onLoad' have the first line...
if (counter > 0) return;
That means that onLoad will fire at window.load and after every script is loaded, but will only execute when it's all loaded.
It's scrappy, but it will solve your problem.
You haven't really described the reason you need to load these libraries asynchronously. Third party libraries often have "on script load" functionality that you can define before the script is loaded. If you need to load multiple libraries before you can execute your code, you may have to either 1. fire up some code every time a library is loaded to test to see if all libraries required are loaded and then fire off you code 2. for every library, create a jQuery promise/deferred to get resolved when that library is loaded and use $.when(promises).done(function/code) to test and run the code whenever a particular set is loaded, or 3. rewrite to use RequireJS. If these libraries are YOUR code, well, you may have to add start up code to your libraries anyway; It might be a good time to learn RequireJS.
I wish I could recommend further, but learning the basics behind RequireJS has always been on my todo list, but it hasn't been done; I just know of people here successfully using it. If that seems like too much trouble, I'd consider some variant of option 2. If you don't know what jQuery would be used eh... you may be stuck with option 1 or 3.
Edit:
Of course, that's not to say that jQuery has got the only promise library, I just often recommend using promises in some form for these kind of things..
Archer's technique looks interesting, I just don't know how reliable it is (it might be quite reliable, I just would like to see proof/documentation). You could combine that with option 2 also, quite well, if you want to short-cut execution for some things while leaving others to be dealt asynchronously and if those script onload methods really work as expected.

JS seems to stay in DOM on Ajax reloaded pages

OK this is an odd error, as it's the first ajax page without any complete reloads I'm creating it's an error I never had to face before... and even the Internet doesn't help me - as it doesn't seem to be an usual problem.
Let's get to the Problem step by step:
I have a page where the Ajax request is triggered by a function which simplified looks like this:
$("a[rel='tab']").live('click',function(e) {
pageurl = $(this).attr('href');
$.ajax({url:pageurl+'?rel=tab',success: function(data){
$('#content').html(data);
}});
if(pageurl!=window.location){
window.history.pushState({path:pageurl},'',pageurl);
}
e.preventDefault();
return false;
});
This is parsed by the PHP File and if the property is set the design won't be loaded but instead just the file itself gets loaded - easy hu?
OK, now the problem itself - it was obvious that the page became slower with the time but I didn't really bother at first as I thought it was some mistake in the designs javascripts - but it wasn't.
So I took a look at the firebug and what I've seen explained a lot -> The Requests stack up with every new page! Of course that would lead into an overkill... Well at first I thought it were just the "setInterval"'s and got a workaround running which resets them on each reload.
But that wasn't the only case they also seem to stack up in "scripts" as I don't think that's a plain history as just the reloaded scripts show up more than once!
Any hints in the right would be really helpfull as I am pretty much stuck here!
OK as it seems the issue is just related to the setInterval after all - After trying around for a while I came to the conclusion that this was the main problem, and it was just some kind of a loop in firebug itself.
However maybe someone gets stuck on the same issue so the solution is pretty much this way:
You have to:
1. Create an array where to store your Intervals
2. call your setIntervals this way
3. Reset them on each "new window"
var intervalArr = new Array();
function killIntervals(){
while(intervalArr.length > 0)
clearInterval(intervalArr.pop());
};
These are the array and the function
So these should be added in your AJAX Request function:
killIntervals();
intervalArr.push(setInterval (yourintervalfunction, 5000));
I hope this will come in handy for someone.

Detect if any JavaScript function is running

I know it may sound very strange, but I need to know if there is any active/running javascript in the page.
I am in situation in which I have to run my javascript/jquery code after everything on the page is rendered and all other scripts have finished.
Is it possible to detect this?
EDIT:
Thank you all for the answers. Unfortunately, I was not able to find a solution, because I have no full control of what is going on the page.
Even, I was able to put my javascript in the end of the page, I think it will not be solution again. The reason is that when the page is rendering a function is triggered, it calls other functions and they calls other and so on. As a result, some of the data is incorrect and that's why i need to run my code to correct it.
I use setTimeout with 2 seconds to ensure that my code will be executed last, but this is ugly...
So, thank you all, but this is more problem with the system, not the js.
JavaScript on web browsers is single-threaded (barring the use of web workers), so if your JavaScript code is running, by definition no other JavaScript code is running.*
To try to ensure that your script occurs after all other JavaScript on the page has been downloaded and evaluated and after all rendering has occurred, some suggestions:
Put the script tag for your code at the very end of the file.
Use the defer and async attributes on the tag (they'll be ignored by browsers that don't support them, but the goal is to make yours the last as much as we can).
Hook the window load event via a DOM2 style hookup (e.g., addEventListener on browsers with standards support, or attachEvent on older IE versions).
In the load event, schedule your code to run after a setTimeout with a delay of 0ms (it won't really be zero, it'll be slightly longer).
So, the script tag:
<script async defer src="yourfile.js"></script>
...and yourfile.js:
(function() {
if (window.addEventListener) {
window.addEventListener("load", loadHandler, false);
}
else if (window.attachEvent) {
window.attachEvent("onload", loadHandler);
}
else {
window.onload = loadHandler; // Or you may want to leave this off and just not support REALLY old browsers
}
function loadHandler() {
setTimeout(doMyStuff, 0);
}
function doMyStuff() {
// Your stuff here. All images in the original markup are guaranteed
// to have been loaded (or failed) by the `load` event, and you know
// that other handlers for the `load` event have now been fired since
// we yielded back from our `load` handler
}
})();
That doesn't mean that other code won't have scheduled itself to run later (via setTimeout, for instance, just like we did above but with a longer timeout), though.
So there are some things you can do to try to be last, but I don't believe there's any way to actually guarantee it without having full control of the page and the scripts running on it (I take it from the question that you don't).
(* There are some edge cases where the thread can be suspended in one place and then allow other code to run in another place [for instance, when an ajax call completes while an alert message is being shown, some browsers fire the ajax handler even though another function is waiting on the alert to be dismissed], but they're edge cases and there's still only one thing actively being done at a time.)
There is no definitive way to do this because you can't really know what the latest is that other scripts have scheduled themselves to run. You will have to decide what you want to target.
You can try to run your script after anything else that may be running when the DOM is loaded.
You can try to run your script after anything else that may be running when the page is fully loaded (including images).
There is no reliable, cross-browser way to know which of these events, the scripts in the page are using.
In either case, you hook the appropriate event and then use a setTimeout() to try to run your script after anything else that is watching those events.
So, for example, if you decided to wait until the whole page (including images) was loaded and wanted to try to make your script run after anything else that was waiting for the same event, you would do something like this:
window.addEventListener("load", function() {
setTimeout(function() {
// put your code here
}, 1);
}, false);
You would have to use attachEvent() for older versions of IE.
When using this method, you don't have to worry about where your scripts are loaded in the page relative to other scripts in the page since this schedules your script to run at a particular time after a particular event.
A way to know when multiple functions have all finished executing
This can be useful if you have to wait multiple API calls or initialisation functions
let processRemining = 0;
async function f1() {
processRemining++
await myAsyncFunction()
processFinished()
}
async function f2() {
processRemining++
await myAsyncFunction2()
processFinished()
}
function processFinished() {
processRemining--
setTimeout(() => { // this is not needed is all the functions are async
if (processRemining === 0) {
// Code to execute when all the functions have finished executing
}
}, 1)
}
f1()
f2()
I often couple it with a freezeClic function to prevent users to interact with the page when there is a script that is still waiting an ajax / async response (and optionnaly display a preloader icon or screen).

Patterns or techniques for ensuring script availability?

As a project I have been working on has grown, so has the frequency of situations where all scripts on a page are not available when other code tries to access them. Though this happens most often after code is updated (e.g. not cached) I've had it come up more and more in testing, when it never used to happen.
I've addressed this, partially, by using a function to wait for a module to become available (see this question) and this addresses the concern, mostly, but I'm not totally thrilled with the implementation, and am looking for a more industrial strength pattern to deal with this. These are possible solutions I've come up with:
1) Load scripts on demand with something like ensure - not ideal. Requires actual script name dependency information to be included in each script, not just module/object name, to do this. Still have to take some action before using a resource to ensure it's available.
2) Manage script loading order. If this would even work (e.g. I don't think that simply putting script A before script B guarantees it will be available since they can be loaded concurrently), it would be a pain, since you don't know a dependency until you've loaded the thing that depends on it. Would require a lot of work to set up on a site that has lots of pages that use different resources (and I have no intention of loading everything used everywhere on the site on every page).
3) Wait for everything to be loaded on a given page before allowing user interaction. Far from ideal for obvious reasons. Still doesn't address dependencies that happen in initialization code.
4) Expand upon my current solution. Currently works like (this is pseudocode, but the basic logic process):
// Depends on Module2
Module1 = (function () {
self = {};
// function requires dependency
// waitFor waits until global named 'dependency' is available then calls callback
self.initialized=false;
self.init = function() {
waitFor('Module2', function() {
self.intialized=true;
});
}
// waitForInitialization sets a callback when self.initialized=true
self.func = self.waitForInitialization(func() {
Module2.doStuff();
});
}
//UI-initiated function requires dependency
self.uiFunc = function() {
if (!self.initialized) {
showPleaseWaitDialog();
self.waitForInitialization(function() {
dismissPleaseWaitDialog();
self.uiFuncImpl);
} else {
self.uiFuncImpl();
}
}
self.uiFuncImpl= function() {
Module2.doStuff();
}
} ());
I can think of ways to create a prototype that would deal with the dependency issue more transparently than my code above, and fully intend to do that if I have to, but is this truly the best solution? What do others do? What are considered best practices?
2) Script Load Order - scripts will always be executed in the order they are placed in the DOM, so while they might load concurrently they will execute in an orderly fashion (faced this same problem on a large project I worked on).
?) If script load order is not an ideal solution for you, you could look into the Promise model.
??) If Promises and Load Order won't work for you, you could listen for a namespaced event that each module could fire when it's initialized, that way if the object exists it can be used and if not its initialization could be listened for.

Categories