calling function in a loop in javascript - javascript

I am trying to call a function from within a loop in a way that when the function finishes executing, the loop continues to the next iteration and calls it again. But instead the loop doesn't wait for the function to finish and instead calls 4 instances of the function and runs them at the same time! Should I put the whole function in the loop or is there to make the loop wait for the function to be executed? Thanks
for (var i=2; i<=4; i++){
galleryAnimation(i); //This is executed 3 times at once
}
function galleryAnimation(i){
$("#pic" + i).delay(delayTime).fadeIn(duration);
}

The function is being executed 3 times just like you requested, the problem is that both delay and fadeIn use timers: they set a timer in the future when the function will be executed and return immediately: they are non-blocking calls. So, in your case, because you're calling the function 3 times at, let's say, 0.0001s, 0.0002s, and 0.0003s, the three kick in at, let's say, 5.0001, 5.0002 and 5.0003.
What you had in mind were blocking calls to these functions. You expected the whole execution to stop until the animations were finished. This would stop the whole browser Javascript engine for the duration, meaning no other animation or user javascript interaction would be possible.
To solve it, you have to use callbacks. You can supply a function to fadeIn that will be called once the animation has completed:
http://api.jquery.com/fadeIn/
You can use queues to simulate the same on delay():
Callback to .delay()

Simplistic solution: Increase the timeout by a factor every time.
var i, factor,
duration = 250,
delayTime = 500;
for (i = 2, factor = 0; i <= 4; i++, factor++) {
galleryAnimation(i, factor);
}
function galleryAnimation(i, factor) {
$("#pic" + i).delay(factor * delayTime).fadeIn(duration);
}
This runs the same way your approach does, only the delays get longer every time.
Generic solution 1 - work with setInterval() to have your worker function (the one that does the fadeIn) called in predefined intervals:
var elements = $("#pic2,#pic3,#pic4").toArray(), // or any other way to select your elements
duration = 250,
delayTime = 500,
intervalId = setInterval(function () {
$(elements.shift()).fadeIn(duration);
if (elements.length === 0) {
clearInterval(intervalId);
}
}, delayTime);
Generic solution 2 - work with callbacks that are called when the previous animation finishes:
var elements = $("#pic2,#pic3,#pic4").toArray(), // or any other way to select your elements
duration = 250,
delayTime = 500,
next = function () {
$(elements.shift()).delay(delayTime).fadeIn(duration, next);
};
next(); // start the chain

one thing you can do, is to use an identifier (boolean) and then, in the loop, you test the identifier to decide if the loop can continue or stop.
For example,
function galleryAnimation(i, iBool){
$("#pic" + i).delay(delayTime).fadeIn(duration);
iBool = 0;
}
Then on the return;
for (var i=2; i<=4; i++){
galleryAnimation(i, iBool);
// If iBool = 0 then continue
// Else Break
}
that might be a solution, as the loop will need the returning value to determine the next step, to continue or break.

I came with my own solution that seemed to work perfectly, this is my code:
function fadeInAnimation(index){
$("#pic" + index).delay(delayTime).fadeIn(duration, function(){
photoIndex();
});
}
function photoIndex(){
index++;
fadeInAnimation(index);
}
fadeInAnimation(index);
});
I have realized that using loops is a really bad idea with things like this. This code will allow me to add as many images as I want just by renaming them in the folder rather than coding them in manually as callbacks for each photo.
Thanks for those who answered. Your suggestions are great and will definitely remember them in other applications like this one

Related

Multiple SetTimeouts execute together

I recently try to learn Webdesign and wanted to do a simple fadeout of an image with JavaScript. I Know there is an easier way with jQuery and after some time with this problem i used that way, but i stumbles across it and want to unterstand why this happens.
To break down the process lets say I have an image and want it to reduce its opacity every 0.1 seconds. Therefore I used a for loop and called a fade function with SetTimeout(fade,100). But every of the 10 loops are executed at the same time. I tried it even more simple with
setTimeout(fade,100);
setTimeout(fade,100); ....
Even then all 10 instances of the funtion get executed at once.
So for future projects: why is that and is there a possible workaround?
Thank you very much
You should use setInterval.
setInterval(fade,100);
setInterval is for a repetitive task. It will keep running. Makes sure you clear interval once you are done to prevent memory leakage.
You are using a for loop, which is not the correct way.
Assume at the current instant the time is: 0
You ran a for loop and created 5 setTimeouts. It takes some time to create this but you can't notice it.
So, each setTimeout should execute after 100 ms.
But they are created at:
1. 0.000000001
2. 0.000000002
3. 0.000000003
4. 0.000000004
5. 0.000000005
And they will call the callback after:
1. 100.000000001
2. 100.000000002
3. 100.000000003
4. 100.000000004
5. 100.000000005
(Just for demonstration)
So, you are not going to notice them and these (setTimeout and setInterval) are not absolute. They make take longer.
Hope it helps. :D
The answer is the event loop. To thoroughly understand this behaviour you have to understand all stages of event loop and especially how setTimeout and setInterval is handled
MDN article on event loop
MDN event loop
Rising stack article will help you in getting a clear understanding of event loop along with micro and macro task
rising stack event loop explained
In nutshell all setTimeout gets processed in same tick of the loop.
Also for your case setInterval is much better
There are multiple ways to achieve it. As others have suggested you can use setInterval or use setTimeout to call recursively. If you must use for loop for some reason, that is also possible.
The basic idea is your call to second setTimeout must be given only after the first execution of fade function or otherwise increase the timeout period between successive calls to setTimeout.
//\//\//\// method 1
function fadeRecur() {
var d1 = document.querySelector( ".d1" );
if(d1.style.opacity == '') d1.style.opacity = 1;
//console.log(d1.style.opacity);
if(d1.style.opacity > 0) {
d1.style.opacity -= 0.1;
setTimeout(fadeRecur, 100);
}
}
setTimeout(fadeRecur, 100);
//\//\//\// method 2
var fsi;
function fadeInter() {
var d2 = document.querySelector( ".d2" );
if(d2.style.opacity == '') d2.style.opacity = 1;
// console.log(d2.style.opacity);
if(d2.style.opacity > 0) {
d2.style.opacity -= 0.1;
} else {
clearInterval(fsi);
}
}
fsi = setInterval(fadeInter, 100);
//\//\//\// method 3
function fadeLoop() {
var d3 = document.querySelector( ".d3" );
if(d3.style.opacity == '') d3.style.opacity = 1;
//console.log(d3.style.opacity);
if(d3.style.opacity > 0) {
d3.style.opacity -= 0.1;
}
}
for(var i=1; i<=10; i++) {
setTimeout(fadeLoop, i*100);
}
.d1 {
background-color: rgba(255, 0, 0, 0.5);
}
.d2 {
background-color: rgba(0, 255, 0, 0.5);
}
.d3 {
background-color: rgba(0, 0, 255, 0.5);
}
<div class='d1'>recursive setTimeout</div>
<div class='d2'>single setInterval</div>
<div class='d3'>setTimeout in for loop</div>
If you want to use setTimeout to change opacity for every 100 ms, you can try this
setTimeout(fade,100);
setTimeout(fade,200);
setTimeout(fade,300);
...
or set a timing variable in loop
for(let timing=100,timeing<1000,timing+=100) setTimeout(fade,timing);
The 10 setTimeout function almost start at same time and trigger at every 100ms till 1000ms. Because it is asynchronous function, it doesn't wait for previous command to finish.

javascript code hanging the page

I am trying to get the position of a moving div(animated using css3 animations) and for checking it continuously I am using a while(true) like below
function detectCollision(){
alert(document.getElementById("obstacle").style.left)
while(true){
var temp = getObstaclePosition();
var temp2 = getPlanePosition();
if(temp[0] <= temp2[0]+500){
document.getElementsByClassName("plane")[0].style.display = "none";
break;
}
}
}
The problem here is that after the first alert the page hangs. Moreover if I put an alert in the while loop then it keeps on popping up and the code works fine but not otherwise.
Let me know how I can fix this?
Instead of using a while (true), which is costly, you can use setInterval:
a = setInterval(function () {
var temp = getObstaclePosition();
var temp2 = getPlanePosition();
if(temp[0] <= temp2[0]+500){
document.getElementsByClassName("plane")[0].style.display = "none";
clearInterval(a);
}
}, 100);
The page does not render while you are inside that loop, and thus the position of the element will not change. This results in the loop never ending.
If you want to do something like this, you will have to be using a recursive setTimeout or a normal setInterval implementation.
With them, do one check per timeout.
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout
JavaScript runs in one and the same thread, so if some loop occupies this with an infinite loop, the rest is not able to run anymore.
As an alternative to your loop, you could use setInterval, which repeatedly executes a function or code snippet:
setInterval(new function() {
// whatever
}, 100); // repeat every 100 ms, i.e. 10 times per second

Function to slow down a loop using callbacks gets messed up when used recursively

note: I may be using the word recursively wrong
I'm trying to create a script that runs through a long string of text, searching for the recurrence of substrings. For this I essentially do a for-loop from 12 to 3 (lengths of strings I want to check for recurrence), and for each of those lengths I use another for loop to define if the string of that length is present multiple times. A dumbed down example (untested, just for demonstration):
for(var len=12; len>3; len--) {
var recurring = [];
for(var pos = 0; pos < (inputString.length - len); pos++) {
var mystring = inputString.substr(pos, len);
// do a regex to check if mystring occurs more than once
// if so, push it into recurring and remove from inputString to prevent partial duplicates when mystring gets shorter
}
// output recurring strings
}
This all works, but obviously when I run this on a very long inputString it gets very slow. In fact, it freezes the browser, and any progress output that I want to show while the script runs is paused and shows up all at once when the script is done.
To prevent this, I created a function that is supposed to run through the for-loop in small batches at a time, using a callback function to proceed to the next batch. By using a basic version of this, I was able to output progress onto the page during what used to be a for-loop, even if it's going through thousands of loops:
var fragmentedFor = function($aFragmentLength, $aTotal, $aFunction, $aCallback)
{
var $lStart = 0,
$lFragmentLength = $aFragmentLength,
$lTotal = $aTotal,
$lFunction = $aFunction,
$lCallback = $aCallback;
// simulate a for loop, but in fragments to prevent the browser from freezing
(function forLoop ($aStart)
{
// run x loops at a time
var $lEnd = $lStart + $lFragmentLength;
// can't run the loop past the total length
if($lEnd > $lTotal )
$lEnd = $lTotal;
// the fragmented for loop
for(var $i = $lStart; $i < $lEnd; $i++) {
// run the function here
$lFunction({count: $i});
}
// next time, start from where we just left
$lStart += $lFragmentLength;
// is there room for more loops?
if($lStart < $aTotal) {
setTimeout(forLoop, 10);
} else {
// if not, run the callback
$lCallback();
}
})();
}
When I run this just once it seems to do what I want:
function myLoop(x) {
new fragmentedFor(
10, // amount of loops per run
x, // total amount of loops
function($aData) { console.log($aData.count); },
function() { console.log('finished') }
);
}
myLoop(1000);
However, when I want to call this one nested within another instance of such a loop, I run into trouble:
new fragmentedFor(
1,
5,
function($aData) { myLoop($aData.count * 100) },
function() { console.log('finished all') }
)
(I have copied my current files here: http://files.litso.com/vigenere/so/, which includes the fragmentedFor function so you can essentially paste these two functions in the console and see the result. This was a lot easier than putting it on jsfiddle and making that work, if you guys don't mind)
What appears to be happening is that the first run of myLoop is started, but since it's trying to run 0 * 100 times it ends safely, going to the second run. The second run of myLoop should go up to 100, but it runs to 19 before suddenly run 3 of myLoop kicks in.
From there on, the output seems to be totally mixed up, I figure because my callbacks are implemented incorrectly and the loops are not really waiting for eachother to finish.
What am I doing wrong here? How can I even start debugging where the problem lies, and how can I make sure that the independent runs of the for loop actually wait for eachother to finish?
-edit-
Here's a jsfiddle of an older working copy: http://jsfiddle.net/u2aKX/
This did not incorporate the fragmentedFor loop, it does have some callback functions which seemed to improve the performance compared to regular nested for-loops by 100%.
I figure because my callbacks are implemented incorrectly and the loops are not really waiting for eachother to finish.
What am I doing wrong here?
Your fragmentedFor expects the $aFunction to be synchronous, and as soon as it returns it schedules the next loop turn.
Yet, by using a nested fragmentedFor in that function, you're making it asynchronous - and the iterations of the inner loops will start to crossfire.
As you recogniced, you will have to let them wait to finish - which means hooking the next iteration on the callback of the previous one. You will have to write another fragmentedFor that deals with asynchronous iteration steps:
function fragmentedForAsync(from, to, body, callback) {
var i = from;
(function loop() {
if (i < to)
body(i++, loop); // pass itself as callback
else
callback();
})();
}
and then use it like this:
fragmentedForAsync(1, 5, function(i, callback) {
fragmentedFor(10, i*100, function(j) {
console.log(j);
}, function() {
console.log('finished '+1);
callback(); // next turn of outer loop
});
}, function() {
console.log('finished all')
});

jQuery functions: why is the completion of the first event delayed until a second event finishes?

This has been bugging me for ages, and so I've created a test jsFiddle to illustrate the situation.
The code is as follows:
$('#test').on('click', function() {
$(this).css('background', 'red');
for (var i = 1; i < 100000; i++) {
var el = document.createElement("div");
el.innerHTML = "Another";
document.getElementById("container").appendChild(el);
}
});
Now, I would have thought that the background color should change first, and then the creation of the children would begin. The color doesn't change, though, until after the for loop has finished.
This is not the case, however, if you put a setTimeout delay in place of the for loop.
var timeoutID = window.setTimeout(function () {
$('#test').css('background', 'blue');
}, 2000);
In the setTimeout case, you see instantaneous red and then blue 2 seconds later. EDIT: Here's the fiddle for the second case.
Why does my first fiddle do what it does? It applies in so many other situations, particularly regarding AJAX.
The browser isn't triggering a repaint until the whole of the function has executed.
You can make it repaint before calling the for loop by wrapping the for in a setTimeout with a timeout of zero, like so:
$('#test').on('click', function() {
$(this).css('background', 'red');
setTimeout(function(){
for (var i = 1; i < 100000; i++) {
var el = document.createElement("div");
el.innerHTML = "Another";
document.getElementById("container").appendChild(el);
}
}, 0);
});
You only have 1 main thread in javascript.
Loops block everything from happening, things like queued setTimeouts() or background repaints.
The difference is, setTimeout() doesn't require heavy computations, because it's just a stack, so your 100,000 iterations loops finishes quicker, and allows the next queued function (in this case, the repaint) to execute.
I have tried a simple modification of it http://jsfiddle.net/Pbgz3/12/ . The color indeed changes before the loop is run. To check that i have decreased the looping iterations.

for loop delay in javascript

How to set delay in for loop execution
for(i=0; i<=10;i++){
var s=i;//This line should execute for every 2 secs only
}
How to give loop delay in java script....
I dont want like below..I want without using setTimeout...
for(i=0; i<=10;i++){
setTimeout("setvalue()",2000); //This alert should display for every 2 secs only
}
function setvalue()
{
var s=i;
}
please help me...
Use setInterval()
var i = 0;
var interval = setInterval(function(){
setValue();
i += 1;
if(i == 10)
clearInterval(interval);
}, 2000);
There is no way to sleep for 2sec without freezing the whole browser. Javascript is single threaded.
You can't. JS runs in a single thread and any attempt to delay that thread will freeze the entire page. Using setTimeout is your only option.
EDIT: or setInterval; either way, there is no non-hairy way to express "halt execution here for x milliseconds."
Using setTimeout is inevitable, however, a recursive function might be a better solution for this one:
var i=0;
function recurs() {
i = s;
i++;
if (i <= 10) recurs();
}
recurs();
As others have stated, setTimeout can be used very well to handle these sorts of scenarios and setInterval could also be used but is discouraged by some.
You can even recursively call a function that has setTimeout built into it as mentioned in the MDN documentation of setInterval. The heading there mentions 'dangerous usage' but their solution to the danger is the block of code beneath.
There it mentions that to have a loop executing every x seconds (or milliseconds) then you can do the following and know for sure that the functions will only be executing one at a time and in-sequence:
(function loop(){
setTimeout(function(){
// logic here
// recurse
loop();
}, 1000); // repeat loop 1 second after this branch has completed
})();
And if you want it to only do that a limited number of times, then you can create a variable outside of the loop and only recursively execute if the count is smaller than the number of times you want to execute for. Such as this:
var count = 0;
(function loop() {
setTimeout(function() {
// logic
count++;
if (count < 10) {
loop();
}
}, 1000);
})();

Categories