Does this Funnelytics code contain a race condition? - javascript

I'm looking into tracking scripts that I've come across. Here's one by Funnelytics. At first look it seems like it has a bug:
(function(funnel) {
var insert = document.getElementsByTagName('script')[0],
script = document.createElement('script');
script.addEventListener('load', function() {
window.funnelytics.init(funnel, false);
});
script.src = 'https://cdn.funnelytics.io/track.js';
script.type = 'text/javascript';
script.async = true;
insert.parentNode.insertBefore(script, insert);
})('8889dbc2-6c2f-5ba4-c201-dc8889dbc26c');
Isn't it possible that the function triggered by load will be called before the asynchronous script track.js gets executed? In which case, won't the line window.funnelytics.init fail, since window.funnelytics hasn't been defined yet?

This code does not contain a race condition. Notice that the event listener is attached to the script element, not the window object:
script.AddEventListener('load', function() { // ...
This function will only get called once the script loads and is executed.
Even if the event listener had been attached to the window object, this code would still not contain a race condition. The function would only get called once all the window's subresources get loaded and executed, including any async scripts that are dynamically inserted, as happens here.

Related

Window onload event from an external JS script is never triggered in React PWA

I am working on a React based PWA. For using an external service I have to add a JS script at runtime. Currently I'm adding it (based on the instructions provided by the service) to the document like so:
function initializeScript() {
const script = document.createElement('script');
script.id = 'source-script';
script.src = 'https://some.url.com';
document.body.appendChild(script);
}
Adding the script is successful as there is the following code available in the browser.
if (document.querySelector('#dom-source')) {
console.info("Binding to the window.onload event");
window.onload = function() {
console.info("The parent window is loaded");
// some code
};
}
The first console.info is being executed. However - and this is my main issue - the window.onload function isn't being executed after that. I guess that's due to the fact that I'm using React and it somehow never triggers the onload event.
Is there a workaround or am I missing something? I can't alter the source code of the added JS code as it's from an external provider. Also I have to add it dynamically after some initialization work of the #dom-source element.
Any help would be much appreciated! Thanks!
UPDATE:
After some trial and error and based on this How do i use external script that i add to react JS? answer I came up with the following semi-working solution:
let A: any;
const scriptLoaded = useCallback(() => {
console.info('script loaded');
// #ts-ignore
A = new A() // A is a class in the external JS file
A.someFunctionOfScript();
}, [])
useEffect(() => {
const script = document.createElement('script');
script.id = 'source-script';
script.src = 'some.url.com'
script.onload = () => scriptLoaded();
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, [token]);
Instead of waiting for the window.onload event, I'm executing the scriptLoaded function. Its code is just pasted from the external files window.onload function.
So this works perfectly up until unmounting or route switching. I'm getting exceptions from the external file. It basically tries to execute the code but the elements like #dom-source are no longer available. Well, at least the script is being executed now.
Anyone got an idea of how to stop the script execution and remove all references to it when unmounting?

if(window.jQuery) returns false after loading jQuery

I have the following JavaScript snippet, which I run in Developer tools console after opening a new tab in Google Chrome with http://example.com
var jq = document.createElement('script');
jq.src = 'https://code.jquery.com/jquery-latest.min.js';
document.head.appendChild(jq);
if (window.jQuery) {
window['jqy'] = jQuery.noConflict();
alert('jQuery is loaded');
}
else {
alert('jQuery not loaded');
}
It alerts jQuery not loaded for the first time, even when I see that the <script> is added to the DOM. Why?
Because the script will load in an async manner and will take a while to load completely after the script tag creation.
The alert method is executed just after the addition of script tag.
To achieve something like this you need a callback which will execute after the script is loaded completely.
Maybe something like this?
function loadScript(url, callback) {
var e = document.createElement("script");
e.src = url;
e.type = "text/javascript";
e.addEventListener('load', callback);
document.getElementsByTagName("head")[0].appendChild(e);
}
loadScript("https://code.jquery.com/jquery-latest.min.js", function() {
// This callback will fire once the script is loaded
if (window.jQuery) {
window['jqy'] = jQuery.noConflict();
alert('jQuery is loaded');
} else {
alert('jQuery not loaded');
}
});
Each Method in the javascript can take some time to complete the desired task allocated to it. Even appendChild function also will take some time to execute the method to complete.
But system won't wait for the task to complete and execute the next line. Because of that for the first time alert is coming with the jquery not loaded.
But if you execute the same function in the console again without loading it will alert you as a jquery is loaded because appendchild method completed his task successfully.
To overcome this issue need to add the task as a callback function. But append child is not having any callback function. So we need to use the setTimeOut function to solve this issue.

Overwriting of the document.write to delay a script's execution

I'm try for delaying the execution of this ad script:
<script type="text/javascript">
var _pop = _pop || [];
_pop.push(['siteId', 809347]);
_pop.push(['minBid', 0.000000]);
_pop.push(['popundersPerIP', 0]);
_pop.push(['delayBetween', 0]);
_pop.push(['default', false]);
_pop.push(['defaultPerDay', 0]);
_pop.push(['topmostLayer', false]);
(function() {
var pa = document.createElement('script'); pa.type = 'text/javascript'; pa.async = true;
var s = document.getElementsByTagName('script')[0];
pa.src = '//URL/pop.js';
pa.onerror = function() {
var sa = document.createElement('script'); sa.type = 'text/javascript'; sa.async = true;
sa.src = '//URL/pop.js';
s.parentNode.insertBefore(sa, s);
};
s.parentNode.insertBefore(pa, s);
})();
</script>
For do this I have apply setTimeout in this way:
setTimeout (function() {
(function() {
var pa = document.createElement('script'); pa.type = 'text/javascript'; pa.async = true;
var s = document.getElementsByTagName('script')[0];
pa.src = '//c1.popads.net/pop.js';
pa.onerror = function() {
var sa = document.createElement('script'); sa.type = 'text/javascript'; sa.async = true;
sa.src = '//c2.popads.net/pop.js';
document.head.appendChild(sa, s);
};
document.head.appendChild(pa, s);
})(); }, 2300);
</script>
And changed s.parentNode.insertBefore with document.head.appendChild
The script start but I not see delay.
I have read "If the target script expects to be run synchronously and uses document.write you're out of luck. Unless, you want to do some messy hacks involving overwriting of the native document.write function.
I need for overwriting document.write?
You can delay the execution of any script by many methods. Simple ones:
Place your script tags just before the end of your page body, so that they are loaded after the browser has parsed the rest of the page. Therefore the browser is able to render the page before your script executes.
Add the defer="defer" attribute to your script tag (if it loads an external file). Execution is delayed at the end of page parsing.
Use a setTimeout wrapped around the code to be delayed, as you tried. Make sure the setTimeout itself is wrapped to be executed on DOM ready event, otherwise it will start the counter at execution, before the rest of the page is rendered, and you may see no effect if that rendering is slow.
Load a script asynchronously after a given timeout (but refer to point 3 about DOM ready event). In your case, why not putting the ad loader code in an external file (so you do not even have to modify it!), and load it later (using a similar script loading method)? Yes, that would be loading a loader…
Side notes:
Use of someScriptElement.parentNode.insertBefore is considered a better practice than document.head.appendChild, as a document may not have a head tag.
The sentence you quoted means that any script that relies on document.write forces you to load it synchronously (and therefore document.write should be avoided whenever possible). You may try to emulate the synchronous load by overwriting the document.write function, so that you could nevertheless load the script asynchronously. But in your case, the ad code does not use document.write, and it loads another script asynchronously, so you do not have to bother.

Trying to fire the onload event on script tag

I'm trying to load a set of scripts in order, but the onload event isn't firing for me.
var scripts = [
'//cdnjs.cloudflare.com/ajax/libs/less.js/1.3.3/less.min.js',
'//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0-rc.3/handlebars.min.js',
MK.host+'/templates/templates.js'
];
function loadScripts(scripts){
var script = scripts.shift();
var el = document.createElement('script');
el.src = script;
el.onload = function(script){
console.log(script + ' loaded!');
if (scripts.length) {
loadScripts(scripts);
}
else {
console.log('run app');
MK.init();
}
};
$body.append(el);
}
loadScripts(scripts);
I guess native events like el.onload don't fire when jQuery is used to append the element to the DOM. If I use native document.body.appendChild(el) then it fires as expected.
You should set the src attribute after the onload event, f.ex:
el.onload = function() { //...
el.src = script;
You should also append the script to the DOM before attaching the onload event:
$body.append(el);
el.onload = function() { //...
el.src = script;
Remember that you need to check readystate for IE support. If you are using jQuery, you can also try the getScript() method: http://api.jquery.com/jQuery.getScript/
I faced a similar problem, trying to test if jQuery is already present on a page, and if not force it's load, and then execute a function. I tried with #David Hellsing workaround, but with no chance for my needs. In fact, the onload instruction was immediately evaluated, and then the $ usage inside this function was not yet possible (yes, the huggly "$ is not a function." ^^).
So, I referred to this article : https://developer.mozilla.org/fr/docs/Web/Events/load
and attached a event listener to my script object.
var script = document.createElement('script');
script.type = "text/javascript";
script.addEventListener("load", function(event) {
console.log("script loaded :)");
onjqloaded(); // in fact, yourstuffscript() function
});
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
For my needs, it works fine now. Hope this can help others :)

why isn't jQuery loading?

When using an importjs() type of function (see below for an example), jQuery doesn't seem to be loading before the code following it.
Here's a sample html file:
<html>
<head></head>
<body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
function importjs(jsFile) {
var body = document.getElementsByTagName('head').item(0);
var scpt = document.createElement('script');
scpt.src = jsFile;
scpt.type = 'text/javascript';
body.appendChild(scpt);
}
var f1="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js";
//importjs(f1);
var $j=jQuery;
alert("hello stackoverflow!");
</script>
</body>
</html>
With the above code, the alert should successfully fire.
Next, comment out the first script block, i.e. the one explicitly loading jQuery, and uncomment the importjs(f1) line in the second script block. This time, the alert does not fire, at least in firefox and safari.
Now, put in an extra alert before the line "var $j=jQuery". For me, it works in both browsers, regardless of how long or short I wait. A setTimeout would probably also do the trick, but it's also not an ideal way to program something like this.
If javascript is single-threaded, why does the importjs fail? Is it because the new element created by importjs doesn't get 'executed' until the first block finishes, or should the new element be executed as soon as it is created?
There are several problems here:
you have jQuery duplicated, one in the html, one in the js
dynamically added javascript won't be available immediately
if you load scripts this way the dependant code should be in a callback function
function importjs(jsFile, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = jsFile;
script.type = 'text/javascript';
script.onload = script.onreadystatechange = function() {
// execute callback
if (callback) callback();
// prevent memory leak in IE
script.onload = null;
head.removeChild(script);
};
head.appendChild(script);
}
then you should use it as:
importjs("jquery.js", function(){
// all jQuery dependant code
// goes here...
});​
UPDATE
There is a more robust solution for including javascript files which allows you to:
include multiple files that are related
ensure they are executed in order
load them in a non-blocking way (parallel with other resources)
I'm still working on this script, but pretty much works right now. Be sure to check it out.
It combines the advantages of different techniques to give a huge benefit on page load time. Here is a related article: Loading Scripts Without Blocking
The syntax is:
include(['jquery.js','jquery-ui.js'], myjQueryCode); // executed in order

Categories

Resources