Busy indicator race condition in Javascript - javascript

I have the following (javascript/jquery) code to show a busy indicator (after a delay) while an image is loading:
function imgUpdate(arg) {
var loaded = false;
$("#image").one("load", function(){
loaded = true;
$("#busyIndicator").hide();
});
setTimeout(function(){
if (!loaded) {
$("#busyIndicator").show();
}
}, 250);
$("#image")[0].src = arg;
}
Sometimes, the indicator comes up and stays up. How is this possible if the browser's javascript engine is single-threaded? (This is on Firefox 3, by the way.)
One note: this seems to happen when the image being loaded is already cached.
Another note: if I log to my firebug console, all of the lines in imgUpdate are executed, but a log message inside the onload handler never prints on subsequent calls to imgUpdate.

Is there any other javascript on the page that breaks? If so, this may not be a race condition -- JS could simply stop executing before the busyIndicator is hidden again...

I'm hard pressed to replicate this.
Here is the implementation of what you're doing:
A version using caching:
http://jsbin.com/uwuho
A version with caching being prevented: (uses parameter to avoid caching)
http://jsbin.com/oguvi
Hit F5/Ctrl-F5 to see it go. (in particular with the version which prevents caching)
With or without caching neither version is doing what you'd described.
Your problem probably lies elsewhere.

Clearing the image's src tag seems to fix the problem:
function imgUpdate(arg) {
var loaded = false;
$("#image").one("load", function(){
loaded = true;
$("#busyIndicator").hide();
});
setTimeout(function(){
if (!loaded) {
$("#busyIndicator").show();
}
}, 250);
$("#image")[0].src = "";
$("#image")[0].src = arg;
}

You might want to clear the timeout in your callback so that it won't fire if the image is loaded.
var timer = null;
function imgUpdate(arg) {
var loaded = false;
timer = setTimeout(function(){
$("#busyIndicator").show();
timer = null;
}, 250);
$("#image").one("load", function(){
if (timer) {
clearTimeout(timer);
timer = null;
}
$("#busyIndicator").hide();
});
$("#image")[0].src = arg;
}

Related

Turbolinks: How to stop running functions when you leave the page?

In my Rails 5.2.2 app I am using Turbolinks.
I have discovered that when I leave a page, the functions that were started continues.
I have organised my functions below a return statement that checks the body class. In my example below, if the body class is not foobar the functions below do not run.
// assets/javascripts/pages/foobar.js
var goLoop;
$(document).on("turbolinks:load", function() {
if (!$("body").hasClass("foobar")) {
return;
}
return goLoop();
});
goLoop = function() {
return setTimeout((function() {
console.log("Hello");
return goLoop();
}), 1000);
};
First time I visit the page, the goLoop function is triggered.
When I follow a link away from the page, the function runs. If I had not used Turbolinks, this would not have happened.
If I follow another link back to the page, the function is triggered again, so now it runs twice.
How can I avoid this, without disabling Turbolinks?
Use the turbolinks:before-cache to teardown your timeout using clearTimeout. You will need to keep a reference of the current timeout ID. So your solution might look like:
var goLoop;
var timeout;
$(document).on("turbolinks:load", function() {
if (!$("body").hasClass("foobar")) {
return;
}
return goLoop();
});
goLoop = function() {
return timeout = setTimeout((function() {
console.log("Hello");
return goLoop();
}), 1000);
};
$(document).on("turbolinks:before-render", function() {
clearTimeout(timeout);
});
You can use PageVisibilityAPI to see is current page active or not.
and for the loop issue, you should check whether it's exist or not then run timeout function.

Prevent calling ajax on scroll when already called

I have a plugin that tells me if an element is visible in the viewport with $('#element').visible() (set to true when visible).
Now I want to create a function that I scroll down a page and load new content with ajax. I have this so far:
window.onscroll = function() {
console.log($('#ele').visible());
if ($('#ele').visible()) {
//ajax call comes here
}
};
As soon as I see the element my log shows true:
I don't have problems implementing the ajax-request now, but shouldn't I block this function to occur only once? How could I prevent that a new element that already has been loaded to load again (prevent using ajax again)?
I thought of using a boolean-variable, but my problem is that I don't know how to implement that because if I set a variable, how would the browser know it's value? Because on every move of my mousewheel it cant remember what that variable's value was?
EDIT:
I tried the code of Ismail and it never reaches the ajax call (alert won't show).
window.onscroll = function() {
var ajaxExecuted = false;
var ele = $('#load_more').visible();
console.log(ele);
return function() {
if (ajaxExecuted) return;
if (ele) {
alert("OK");
var ajaxArray;
ajaxArray = { page: 2 }
ajaxLoadContent(ajaxArray, "load_more", "ajax_load");
ajaxExecuted = true;
}
}
};
You can use this:
window.onscroll = (function() {
var ajaxExecuted = false;
return function() {
if(ajaxExecuted) return;
if ($('#ele').visible()) {
$.ajax({...}).success(function() {
//Your code here;
ajaxExecuted = true;
});
}
}
})();
One easy solution: set a boolean to true when the element first becomes visible and set it to false when it stops being visible. Only fire the request if those states mismatch (i.e. if it's visible but the boolean is false - that means it's the first time you've seen the window. You'd then set the bool afterwards so it won't fire off anymore until it disappears and reappears again).

How do I unblock the rendering of my web page if an external resource isn't loading on Firefox?

I don’t want to delay rendering of my page if an external resource takes a while to load and so I implemented this logic …
<script type="text/javascript">
function importScript (sSrc, fOnload) {
var oScript = document.createElement("script");
oScript.type = "text\/javascript";
oScript.defer = true;
if (fOnload) { oScript.onload = fOnload; }
document.currentScript.parentNode.insertBefore(oScript, document.currentScript);
oScript.src = sSrc;
}
importScript(“//thirdpartysite.com/theirscript.js", function () { doStuff(); });
});
</script>
This works great except on Mac Firefox (I’m using version 45.0.1). ON that browser, the page does not render until this resource has been loaded. Does anyone know a way to defer loading of the resource without blocking rendering of the page that preferably works on both Chrome and Firefox (all browsers would be nice, but I don’t have time to test them all).
Since you already have a system that takes an URL and then calls a callback, you can easily insert a setTimeout there. Something like
function importScript(sSrc, fOnload) {
setTimeout(function () {
var oScript = document.createElement("script");
oScript.type = "text\/javascript";
oScript.defer = true;
if (fOnload) {
oScript.onload = fOnload;
}
document.currentScript.parentNode.insertBefore(oScript, document.currentScript);
oScript.src = sSrc;
}, 100);
}
And of course, execute the imports on the DOMReady event. This will pretty much hack any rendering blocking that may occur.
However the system becomes exceedingly complex when you add dependencies, code that needs to be executed only if the component has loaded completely. That leads me to believe that a more fluent interface (like a Promise system) could improve your design and also maybe trickle the timeout and even the deferred execution until DOMReady has been fired to all the elements involved.
Here is an example without a promise like syntax, but assuming all the third party scripts and dependencies start from one point only:
var loadingChainStarted = false;
function importScript(sSrc, fOnload) {
if (document.readyState !== "complete") {
window.addEventListener("onload", function () {
importScript(sSrc, fOnload);
}, false);
return;
}
if (!loadingChainStarted) {
loadingChainStarted = true;
setTimeout(function () {
importScript(sSrc, fOnload);
}, 100);
return;
}
var oScript = document.createElement("script");
oScript.type = "text\/javascript";
oScript.defer = true;
if (fOnload) {
oScript.onload = fOnload;
}
document.currentScript.parentNode.insertBefore(oScript, document.currentScript);
oScript.src = sSrc;
}
This will only execute the script after DOMReady and, for the first time, after a 100 millisecond delay. The rest of the chain would be executed normally as both DOMReady has been fired and the delay has elapsed.

need to set a timer for in javascript , then clear

I want to create a timer in JavaScript. I see the setTimeout(fn, 100) but unclear how to wrap this so it will clear itself at the end.
I tried doing
var buttonTimer = null;
$scope.backButton = function() {
if(buttonTimer === null){
$history.back();
}
buttonTimer = setTimeout(function(buttonTimer){
buttonTimer = null;
}, 100);
}
The whole point is to prevent the user from hitting this function too quickly.. and ignoring all subsequent clicks within that 100ms window, at the end of the window, clear the timer and resume accepting clicks
Since you are doing angular, I prepared a plnkr for demonstration:
http://plnkr.co/edit/5qrslKpmkglXTvEyYgBr?p=preview
Your code is almost Ok, the only problem is that you start a new timeout on every click. The effect is, that the callback fires multiple times and resets buttonTimer.
So the only change is:
var blocker = null;
$scope.backButton = function() {
if(blocker == null) {
blocker = setTimeout(function(){
blocker = null;
}, 1500);
// DO YOUR STUFF HERE
}
};
You can use throttle from lodash/underscore or Ramdajs.
for example
$scope.backButton=_.throttle(100,function(){/* do something here */});

Scraping Trophy Data from The Official Playstation Website

Im trying to use PhantomJS to scrape the trophy data from http://my.playstation.com/logged-in/trophies/public-trophies/
The page requires you enter a valid username and then click 'go' and the page will load the data. Ive gotten this to work somewhat, but it never loads the trophy data into the div. Im hoping im missing something ajax related thats causing this?
var fullpagehtml = page.evaluate(function()
{
document.getElementById("trophiesId").value = "<<valid user id>>";
//checkPTrophies(); btn click calls this function
$('#btn_publictrophy').click().delay( 6000 );
console.log("\nWaiting for trophy list to load...");
var trophylist = document.getElementById("trophyTrophyList").innerHtml; // all the data i want ends up inside this div
var counter = 0; //delay andset timeout wont work here so this is the best i coukld think of
while (trophylist == null)
{
//presumably the ajax query should kick in on the page and populate this div, but it doesnt.
trophylist = document.getElementById("trophyTrophyList").innerHtml;
counter ++;
if(counter == 1000000)
{
console.log($('#trophyTrophyList').html());
counter = 0;
}
}
return document.all[0].outerHTML;
});
The delay( 6000 ) does absolutely nothing as the documentation says:
The .delay() method is best for delaying between queued jQuery effects. Because it is limited—it doesn't, for example, offer a way to cancel the delay—.delay() is not a replacement for JavaScript's native setTimeout function, which may be more appropriate for certain use cases.
To wait you have to do this outside of the page context (busy waiting doesn't work in JavaScript because it is single threaded):
page.evaluate(function() {
document.getElementById("trophiesId").value = "<<valid user id>>";
//checkPTrophies(); btn click calls this function
$('#btn_publictrophy').click();
});
console.log("\nWaiting for trophy list to load...");
setTimeout(function(){
var fullpagehtml = page.evaluate(function() {
var trophylist = document.getElementById("trophyTrophyList").innerHTML;
return trophylist;
});
}, 20000);
You also might want to use waitFor to wait until #trophyTrophyList is populated instead of using setTimeout:
waitFor(function(){
return page.evaluate(function(){
var e = document.getElementById("trophyTrophyList");
return e && e.innerHTML;
});
}, function(){
// TODO: get trophies
});
This won't get you far, because just because #trophyTrophyList is loaded, doesn't mean that the descendent elements are already in the DOM. You have to find some selector which signalizes that the page is sufficiently loaded for example by waiting until a .trophy-image exists in the page. It works for me with a 20 second timeout of the waitFor function.
waitFor(function(){
return page.evaluate(function(){
var e = document.querySelector("#trophyTrophyList .trophy-image");
return e;
});
}, function(){
setTimeout(function(){
var trophiesDiv = page.evaluate(function(){
return document.getElementById("trophyTrophyList").innerHTML;
});
console.log(trophiesDiv);
}, 1000); // wait a little longer
}, 20000);
Don't forget that you need page.evaluate to actually access the DOM. Btw, it is innerHTML not innerHtml.

Categories