setInterval() halts while code is executing - javascript

I was under the impression that setInterval(function, delay) would schedule a call to function every delay milliseconds. It would repeatedly do this until clearInterval() was called. However, it seems I'm missing something.
I have the following sample page. The intent is simple: have the text switch from Loading -> Loading. -> Loading.. every second. Here's a sample of the loop working as expected:
$(document).ready(function(){
$('#loading_icon').html("Loading");
loop = setInterval(function() {
updateText();
}, 1000);
});
function updateText() {
loadingText = $('#loading_icon').html();
$('#loading_icon').html(loadingText == "Loading" ? "Loading." :
loadingText == "Loading." ? "Loading.." :
"Loading");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="loading_icon">Loading</div>
Nothing to it. The issue is that it doesn't execute this code so long as JavaScript is doing anything else. I've set the up following example:
$(document).ready(function(){
$('#loading_icon').html("Loading");
loop = setInterval(function() {
updateText();
}, 1000);
for(i = 0; i < 100000; i++)
var a = fib(i); // Just an arbitrary method to simulate work
});
function fib(n){
var a=1,b=0,t;while(n>=0){t=a;a=a+b;b=t;n--;}return b;
}
function updateText() {
loadingText = $('#loading_icon').html();
$('#loading_icon').html(loadingText == "Loading" ? "Loading." :
loadingText == "Loading." ? "Loading.." :
"Loading");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="loading_icon">Loading</div>
Ideally, the text would be changing while we were doing all this work (that's the whole reason I'm writing this "loading" feature, after all). However, it seems that no calls to updateText() are being called until JS has nothing better to do.
What am I missing here? Is there any way to accomplish what I'm after?

Your understanding of setInterval() seems to be correct. It should not be halting code outside of its callback function. Most likely your code is getting caught up somewhere else.
As for accomplishing what you're trying to do, your solution should work but it's not a very elegant way of manipulating the DOM. I see that you're using jQuery, which has pretty robust animation functionality. I would look into using something like this.

Yes, it still works - the problem is that your loop over fib is running first, then setInterval is running later. You can decrease the loop length to see it working perfectly:
$(document).ready(function() {
$('#loading_icon').html("Loading");
loop = setInterval(function() {
updateText();
}, 1000);
for (i = 0; i < 100000; i++)
var a = fib(i); // Just an arbitrary method to simulate work
});
function fib(n) {
var a = 1,
b = 0,
t;
while (n >= 0) {
t = a;
a = a + b;
b = t;
n--;
}
return b;
}
function updateText() {
loadingText = $('#loading_icon').html();
$('#loading_icon').html(loadingText == "Loading" ? "Loading." :
loadingText == "Loading." ? "Loading.." :
"Loading");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="loading_icon">Loading</div>
Unfortunately, you can't make an extremely long loop and a setInterval run at exactly the same time.

Related

Javascript output during function loop

Is there an easy way to output content when inside a Javascript loop, rather than have it display on screen after the loop has completed.
Code e.g:
var c = 0;
while (c <=1000 ){ //100000
run();
c++;
}
function run() {
console.log(c);
$('#data').append(c);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<div id="data"></div>
It outputs to console straight away (during loop) but on screen does not.
Hoping someone can assist.
Thanks!
Are you wanting to write it to the webpage?
If so then you can write it to a div using the InnerHTML
document.getElementById("yourDivID").innerHTML = yourString;
Your browser's Javascript engine is too fast thus you cannot see the changes in real time. So set a timer and slow down the process.
Run the below code and see the magic happens...
var c = 0;
$(document).ready(function () {
run();
});
function run() {
var timer = setInterval(function () {
//console.log(c);
$('#data').append(c + "\n");
if (c++ == 1000) {
clearInterval(timer);
}
}, 12); //set time in milliseconds
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="data"></div>
These are the changes made to your code :
Removed the while loop and replaced it with setInterval()
Added $(document).ready() function to make sure the run() is executed after the DOM is fully loaded.
Try using clousres and setTimeout:
function run(c) {
console.log(c);
$('#data').append(c + ', ');
}
$(function() {
for (var c = 1; 999 > c; c++) {
(function(c) {
setTimeout(function() {
run(c);
}, 1);
})(c);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="data"></div>

How do I make jQuery statement execute after .load()?

Sorry for the possibly terrible question title. I don't know how to word it properly. Please offer suggestions.
Anyhoo, I have this html:
<ul class="image_reel">
<li class="thin"><img class="gal_thumb" src="8681.jpg"></li>
<li class=""><img class="gal_thumb" src="DSC_7586.jpg"></li>
<li class="thin"><img class="gal_thumb" src="DSC_7601.jpg"></li>
</ul>
I want to assign the li either a 'thick' or 'thin' class based on the height of the child img. So I have this jQuery:
// add 'thin' class to parent li
jQuery('.image_reel li a img').load(
function() {
var t = jQuery(this);
var h = this.naturalHeight;
if( h < 800 ) {
jQuery(t).parent().parent().addClass( 'thin' );
}
});
// now sort lis
var $images = jQuery('.image_reel');
var $imagesli = $images.children('li');
$imagesli.sort(function(a,b){
var aT = jQuery(a).hasClass('thin');
var bT = jQuery(b).hasClass('thin');
if( aT == true && ( bT == false ) )
return 1;
else
return 0;
});
$imagesli.detach().appendTo($images);
The problem seems to be that the first block seems to execute -after- the second block. Or maybe they execute synchronously? Regardless, the code doesn't work. So... how do I make the first block of code execute -before- the 2nd?
The weird thing is that, if I use the Firefox 'Q' debugger, the code actually 'works'. But without the debugger it doesn't. I assume that the debugger forces the code to run in some sort of special order.
Wrap your section block in a function, and then call it after the load function ends, like put it in the return
// add 'thin' class to parent li
jQuery('.image_reel li a img').load(function() {
var t = jQuery(this);
var h = this.naturalHeight;
if( h < 800 ) {
jQuery(t).parent().parent().addClass( 'thin' );
}
return loadBlock();
});
function loadBlock() {
// now sort lis
var $images = jQuery('.image_reel');
var $imagesli = $images.children('li');
$imagesli.sort(function(a,b){
var aT = jQuery(a).hasClass('thin');
var bT = jQuery(b).hasClass('thin');
if( aT == true && ( bT == false ) ) {
return 1;
} else {
return 0;
}
});
$imagesli.detach().appendTo($images);
}
Or use a package like async .waterfall or .series
You can use something like async as I mentioned above to have better flow control over async functions here's an example on how you could avoid calling on every callback
async.each($("img"), function(e, callback) {
$(this).load(function() {
console.log("bdbdbd");
callback(); // Done loading image
});
}, function() {
console.log("Done loading images");
loadBlock();
});
Async package can and will be your best friend if utilized properly.
I'm on mobile so I can't really test but this worked just fine on jsbin just throw your code in there and it should work.
this works because when you call jQuery('.image_reel li a img').load this will run its own loop on every element it finds and attach the event listener to it.
The method I used was I used async.each which takes an array, in this case I provided $('#img') as the array which will be a collection of any element it finds that matches the query, then async.each will run the loops in parallel so we don't have to wait for one to finish before the loop can proceed to the next thing.
Then in the loop body we call .load on .this which is attaching the .load function on only 1 element at a time and not trying to do its own internal loop on all the elements, so this way when the function completes we know that its done cause that function is only running on on element. Then we call callback(); which is required for async.each to let .each know that the function body is done and it can proceed, when all loops trigger their callback the loop ends and then the main function executes (the function that's the third argument to .each). You can see more about async.each here: async.each
The second block executes before the first block because the load() resolves immediately and only calls the first block later when it has finished. To do what you want, call the second block at the end of the first block.
// add 'thin' class to parent li
jQuery('.image_reel li a img').load(
function() {
var t = jQuery(this);
var h = this.naturalHeight;
if( h < 800 ) {
jQuery(t).parent().parent().addClass( 'thin' );
}
doWork();
});
function doWork() {
// now sort lis
var $images = jQuery('.image_reel');
var $imagesli = $images.children('li');
$imagesli.sort(function(a,b){
var aT = jQuery(a).hasClass('thin');
var bT = jQuery(b).hasClass('thin');
if( aT == true && ( bT == false ) )
return 1;
else
return 0;
});
$imagesli.detach().appendTo($images);
}

JQuery not executing within a For loop

Im a JQuery noob trying to write a simple jQuery code to get a text to blink three times. My initial code was as follows:
$("#welcome").click(function () {
var i = 1;
while (++i < 10) {
$("#welcome").fadeOut("slow", function () { $("#welcome").fadeIn("slow"); })();
}
});
But since I probably meddled in forces I could not comprehend, the above code made the text blink only once. I read up on closures and got convinced that the below code could make a change. Unfortunately, it doesnt.
$("#welcome").click(function () {
var i = 1;
while (++i < 10) {
(function (i) {
$("#welcome").fadeOut("slow", function () { $("#welcome").fadeIn("slow"); })();
})(i);
}
});
Can anyone tell me whats going on here?
You need make use of the animation queue
var $welcome = $("#welcome").click(function () {
var i = 1;
//clear previous animations
$welcome.stop(true, true);
while (++i < 10) {
$welcome.fadeOut("slow").fadeIn("slow");
}
});
Demo: Fiddle
Fading in and out takes some time, and you have to wait for your animation to be over before you can run the next one.
The provided answers solve your problem since jQuery is clever enough to bufferize your animation queue, but it may creates even more confusion for begginers, and also if you want to do something else between the fading animations, you can't rely on it anymore.
You then have to write your code on what is called an asynchronous recursive way (woah). Simply trying to understand that snippet may help you a lot with javascript general programming.
function blink(nbBlinks) {
// Only blink if the nbBlinks counter is not zero
if(nbBlinks > 0) {
$('#welcome').fadeOut('slow', function() {
// Do stuff after the fade out animation
$(this).fadeIn('slow', function() {
// Now we're done with that iteration, blink again
blink(nbBlinks-1);
})
});
}
}
// Launch our blinking function 10 times
blink(10);
This works perfectly. Demo http://jsfiddle.net/X5Qy3/
$("#welcome").click(function () {
for (var x = 0; x < 3; x += 1) {
$("#welcome").fadeOut("slow");
$("#welcome").fadeIn("slow");
}
});
Also, if you know how many times you want to do something. You should use a For Loop. While Loops are for when you don't know how many times you want it to run.
Set in queue
$("#welcome").click(function () {
var i = 1;
//clear animations whcih are running at that time
$(this).stop(true, true);
while (++i < 10) {
$(this).fadeOut("slow").fadeIn("slow");
}
});
You can not use jQuery delay function inside a looping/iteration hence you have to user closures:
$(document).ready(function(){
$(".click1").click(function () {
for (i=0;i<=10;i++) {
setTimeout(function(x) {
return function() {
$("#wrapper").fadeOut("slow", function () { $("#wrapper").fadeIn("slow"); })();
};
}(i), 1000*i);
}
});
});
<div id="wrapper"></div><div class="click1">click</div>
You can later change the count how many times you want to blink the <div>.

Timer won't die javascript

I am doing some long polling.. and I have a function to make a button blink when a certain statement is true.. Here it is:
function blinking(object, x) {
console.log(x);
if(x>0){
var existing_timer = object.data('clock');
if (existing_timer){
clearInterval(existing_timer);
}
timer = setInterval(blink, 10);
function blink() {
object.fadeOut(400, function() {
object.fadeIn(400);
});
}
}
}
Now.. Notice the timer being set as 'timer'. When someone does something that makes the statement false (making x=0), I want the timer to stop making the button blink when it sees that x=0. This may sound easy but I have tried everything ha.
I've been researching and trying different things, but It doesn't seem to work.
If your variable timer is global, then you should be able to clear the interval using that:
clearInterval(timer);
The integer returned from the setInterval is unique to that timer, so that is what you need to clear.
Here's a simple implementation.
http://jsfiddle.net/AQgBc/
var $led = $('#led'),
blinkState = 0,
blinkTimer,
blinkDuration = 300,
blink = function () {
if (blinkState) {
$led.toggleClass('on');
}
console.log('blink');
blinkTimer = setTimeout(blink, blinkDuration);
};
$('button').on('click', function () {
blinkState = (blinkState) ? 0 : 1;
});
blink();
I just wrote my own without duplicating yours, but the most relevant issue, I think, is just using a setTimeout rather than worrying about a setInterval and clearing that interval.
Edit in response to comment:
If you want it to blink until a user action, then stop polling, it's even simpler.
http://jsfiddle.net/AQgBc/1/
var $led = $('#led'),
blinkState = 1,
blinkTimer,
blinkDuration = 300,
blink = function () {
if (blinkState) {
console.log('blink');
$led.toggleClass('on');
blinkTimer = setTimeout(blink, blinkDuration);
}
};
$('button').on('click', function () {
blinkState = (blinkState) ? 0 : 1;
});
blink();

How to create a looped animation with JQuery

I have been sitting on this for a few hours and cannot figure this out. I am trying to create an slideshow (3 slides) that loops endlessly. Each slide is a li inside #slideshow. I have walked through this with a debugger and all variables get set correctly, but I don't understand why the animations dont actually happen. I have this which ends up displaying all images on the page:
$(document).ready(function() {
$slideshow = $('#slideshow');
$slideshowItems = $slideshow.find('li');
$slideshowItems.hide();
nextI = function(x) {
if ((x+1) < $slideshowItems.length) {
return x+1;
}
else {
return 0;
}
}
animation = function(i) {
$slideshowItems.eq(i).fadeIn(500).delay(1000).fadeOut(500, animation(nextI(i)));
}
animation(0);
If I do:
$slideshowItems.eq(0).fadeIn(500).delay(1000).fadeOut(500,
$slideshowItems.eq(1).fadeIn(500).delay(1000).fadeOut(500,
$slideshowItems.eq(2).fadeIn(500).delay(1000).fadeOut(500));
This works as expected, but it seems ugly and does not loop.
Any idea why I can't get this to work? I feel it is something with my expectations of how JQuery/ JS modifies the DOM or the sequence that the browser uses to execute animations. Thank you for the help!
var $slideshowItems = $('#slideshow').find('li'),
i = 0;
(function loop() {
$slideshowItems.eq( i ).fadeIn(500).delay(1000).fadeOut(500, loop);
i = ++i % $slideshowItems.length;
})();
JSFIDDLE DEMO
You should specify a callback method but your "animation(nextI(i))" returns nothing, so nothing remains to do after the fade out is complete.
Something like this I think will work:
var animation = function(i) {
$slideshowItems.eq(i).fadeIn(500).delay(1000).fadeOut(500, function (){
animation(nextI(i));
});
}
I would try setting that as a function and then using setInterval:
setInterval(function(){
$slideshowItems.eq(0).fadeIn(500).delay(1000).fadeOut(500, function() {
$slideshowItems.eq(1).fadeIn(500).delay(1000).fadeOut(500, function() {
$slideshowItems.eq(2).fadeIn(500).delay(1000).fadeOut(500);
});
});
}, 6000); // 6000 milliseconds before loops

Categories