Queue function call in javascript - javascript

I have functions that contain timed animation and i want these functions to run one after the other just when the previous one is done.
function a() {
var i = 0;
var x = setInterval(function () {
console.log('a' + i);
if (i == 3) {
console.log('Done #' + i);
clearInterval(x);
return true;
}
i++;
}, 1000);
}
function b() {
var c = 0;
var y = setInterval(function () {
console.log('b' + c);
if (c == 1) {
console.log('Done 2 #' + c);
clearInterval(y);
return true;
}
c++;
}, 1000);
}
a().then(b());
I tried one here but it's not working. Here's the fiddle.
What i want to achieve is like this:
a0
a1
a2
a3
Done #3
b0
b1
Done 2 #1
but it stops at Done #3. Im looking for any other ways to achieve this.

If you want to call then method – function must return promise, after then you don’t want to immediate call b() but pass reference to it a().then(b);
function a() {
var i = 0;
var defer = $.Deferred();
var x = setInterval(function () {
console.log('a' + i);
if (i == 3) {
console.log('Done #' + i);
clearInterval(x);
defer.resolve('asdasd');
}
i++;
}, 1000);
return defer;
}
function b() {
var c = 0;
var y = setInterval(function () {
console.log('b' + c);
if (c == 1) {
console.log('Done 2 #' + c);
clearInterval(y);
return true;
}
c++;
}, 1000);
}
a().then(b);
jsfiddle: http://jsfiddle.net/Lungx/3/

Two simpler approaches that should generally work, though maybe not in your case.
(1) jQuery. If you're using the jQuery animate() function, then you can set each animation in the callback for the animation previous. That way when animation n finishes, your callback function will start the animation for n+1, etc. There is a discussion and a couple examples here.
(2) If you're animating (or transitioning, really) CSS, you can attach a handler to the transitionend event. That way, similar to the first approach, you can have the next transition begin as soon as the previous transitionend event fires. See here, especially under the header "Detecting the completion of a transition."
I've used the first approach before, and I don't see any reason why the second shouldn't work. I hope this helps. Let me know if it doesn't work; I'm curious.

Related

Executing an animation with for loop in callback function

Inside updateMapColor function, the world map changes colors based on the input year's value. I am trying to animate the color change over a sequence of years by calling updateMapColor, but it's not working.
Do I need to use setInterval, if so why? Could someone explain the underlying issue please?
d3.select('body').append('button').attr({
class: "button",
id: "animateMap"
})
.text("Animate the map")
.on("click", function (d) {
for (i = 0; i < yearArray.length; i++) {
updateMapColor[yearArray[i]]
}
})
var updateMapColor = function (y) {
year = y;
var rankingList = {}
coloredWorldMap.transition().duration(700).attr('fill', function (d) {
a = d;
x = d3.values(tennis).filter(function (d) {
return (d.DATE == year)
})
rankingList = x;
x = d3.values(x).filter(function (d) {
return (d.COUNTRY_ID == a.id)
})
if (x.length != 0) {
if (x[0].COUNT == 0) {
return "#fff"
}
else {
return colorScale(x[0].COUNT)
}
}
else {
return '#fff'
}
return colorScale(x[0].COUNT)
})
This is a classic problem in javascript: trying to call a function inside a for loop with some delay.
The reason your code doesn't work is simple: the for loop will run almost immediately (in just a few miliseconds for a hundred or so loops), and all the calls to updateMapColor will happen almost simultaneously.
There are different ways to fix this. One of my favourite solutions is using an IIFE:
(function loop(i) {
if (i++ >= (yearArray.length - 1)) return;
setTimeout(function() {
updateMapColor(i);
loop(i)
}, 500)
})(-1);
Explanation:
This IIFE will run for the first time with i = -1. Then, the if increases i (doing i++, which makes i effectively starting at 0, just like in your for loop) and checks if it is equal or bigger than yearArray.length - 1. If it is, the function loop returns. If it's not, a setTimeout is scheduled, which calls updateMapColor and the very function loop again.
Check the demo, with a setInterval of 0.5 seconds:
var yearArray = d3.range(20);
(function loop(i) {
if (i++ >= (yearArray.length - 1)) return;
setTimeout(function() {
updateMapColor(i);
loop(i)
}, 500)
})(-1);
function updateMapColor(index){
console.log("The function was called with i = " + index);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: whatever you're trying to do, it'd be way easier just chaining a bunch of D3 transitions.

fail to setTimeout in a for loop

I'm building a simon game. And after each round the player should see the moves he must play in the next round. So i created a function showMoves which flashes the square he has to play. The problem is that the function is not showing anything. Can anyone tell me what did i miss?
// the effect
function flasher(index) {
$(moves[index]).fadeIn(50).fadeOut(50).fadeIn(50).fadeOut(50).fadeIn(100);
}
var interval2;
// show the moves that supposed to be played
function showMoves() {
for (var i = 0; i < moves; i++) {
if (i === 0) {
interval2 = setTimeout(flasher(i), 1000);
} else {
interval2 = setTimeout(flasher(i), (i+1) * 1000);
}
}
}
setTimeout accepts a function as a first parameter. I assume that by calling flasher you tried to avoid this situation. In you case, this should be done like this:
function showMoves() {
for (var i = 0; i < moves; i++) {
if (i === 0) {
interval2 = setTimeout(function(i) {return function() {flasher(i)}}(i), 1000);
} else {
interval2 = setTimeout(function(i) {return function() {flasher(i)}}(i), (i+1) * 1000);
}
}
}
The setTimeout and setInterval are a little diffrent than we think about them.
They are save some event on specified times that will be fired in its times. Because of this they has a problem with loops:
for(i=0;i<3;i++)
{
setTimeout(function(){alert(i)}, i*1000);
}
after ending the loop the browser has 3 jobs to do:
alert(i) after 1 second
alert(i) after 2 seconds
alert(i) after 3 seconds
But what is the value of 'i'. If you are in c# programming after ending the loop 'i' will be disposed and we have not that.
But javascript does not dispose 'i' and we have it yet. So the browser set the current value for i that is 3. because when 'i' reaches to 3 loop goes end. Therefor Your browser do this:
alert(3) after 1 second
alert(3) after 2 seconds
alert(3) after 3 seconds
That is not what we want. But if change the above code to this:
for(i=0;i<3;i++){
(function (index)
{
setTimeout(function () { alert(index); }, i * 1000);
})(i);
}
We will have:
alert(0) after 1 second
alert(1) after 2 seconds
alert(2) after 3 seconds
So as Maximus said you mast make the browser to get value of i currently in loop. in this way:
setTimeout(function(i) {return function() {flasher(i)}}(i), (i+1) * 1000);
i does not leave out until end of loop and must be get value just now.
What I can derive from your code is that moves is an array, but you're using it as if it's an integer in the for loop. And that's why nothing happens at all.
Replace:
for (var i = 0; i < moves; i++) {
With:
for (var i = 0; i < moves.length; i++) {
And you should see things happening.
But you will notice flasher is called immediately, without timeout. And that's because the result of flasher is set to be called, instead of flasher itself.
Other answers here suggest using an wrapper function, but this requires workarounds to correctly pass the index to the function called by setTimeout.
So assuming that it doesn't have to run in IE8 and below, the following is the most concise solution:
setTimeout(flasher.bind(null, i), (i+1) * 1000)
Full working example:
var moves = [1, 2, 3, 4];
function flasher(index) {
console.log('move', moves[index]);
}
var interval2;
// show the moves that supposed to be played
function showMoves() {
for (var i = 0; i < moves.length; i++) {
interval2 = setTimeout(flasher.bind(null, i), (i+1) * 1000);
}
}
showMoves()

Incorrect syntax on setTimeout

I'm trying to make a faux loading screen, and I need delays between loading messages of about 20-50ms or so so that people can actually see what's going on before it cuts to the initialized screen. The button that activates this goes to the following function:
function gameinit() {
for (k = 0; k <=1; k += 0.125) {
setTimeout(function () {
var nexttxt = "Loading... " + toString(100 * k) + "%"
}, 20);
displayupdate(nexttxt);
}
}
However this comes up as an incorrect syntax (on JSfiddle - https://jsfiddle.net/YoshiBoy13/xLn7wbg6/2/) when I use JShint - specifically lines four and five. I've looked at the guides for this and everything seems to be in order. What am I doing wrong?
(Note: displayupdate(nexttxt) updates the <p> tags with the next line of text)
When executing the script, nothing happens - the sixteen lines of text on the HTML move up as normal, the top eight being replaced with the eight generated by the gameinit() function, but the gameinit() only generates blank. If the script is executed again, it just outputs eight lines of 112.5% (as if it was the 9th iteration of the for loop).
I'm almost certain it's something elementary that I've missed, could someone please tell me what I've done wrong?
Use setInterval() instead, you can clear interval using clearInterval()
function gameinit() {
displayupdate("Loading... 0%");
var k = 0;
var inter = setInterval(function() {
if (k < 1) {
k += .25;
displayupdate("Loading... " + 100 * k + "%")
} else {
clearInterval(inter);
}
}, 2000);
}
function displayupdate(d) {
console.log(d);
}
gameinit();
here is another function can do this better ---- setInterval
var txt = '';
var time = 0;
var id = setInterval(function(){
console.log("loading..."+time/8*100+"%");
if(time++>7)
clearInterval(id);
},1000);
setTimeout doesn't work as you would expect it to work inside loops. You have to create a closure for each loop variable passed on to setTimeout, or create a new function to execute the setTimeout operation.
function gameinit() {
for (var k = 0; k <= 1; k += 0.125) {
doSetTimeOut(k);
}
}
function doSetTimeOut(k) {
setTimeout(function() {
var nexttxt = "Loading... " + toString(100 * k) + "%"
}, 20);
displayupdate(nexttxt);
}

HTML5 WebWorkers, timed function

I am trying to get a WebWorker to count to 100 and update a div with the value of I, currently the div just updates straight to 100 and seems to ignore the interval....
JavaScript (webworker file):
self.addEventListener('message', function (e) {
switch (e.data) {
case 'Hi Worker':
postMessage('Hi Boss');
break
case 'Count to 100':
var i;
for (i = 0; i < 100; i++) {
setInterval(postMessage(i + 1), 1000);
}
break;
default:
self.postMessage("Not sure how to help with that");
}
}, false);
Main file:
<script>
var worker = new Worker('worker.js');
worker.addEventListener('message', function (e) {
console.log("worker said: " + "'" + e.data + "'");
document.getElementById("workerComms").textContent = "worker said: " + e.data;
}, false);
</script>
</head>
<body>
<button onclick="worker.postMessage('Hi Worker');return false;">Say 'Hi Worker'</button>
<button onclick="worker.postMessage('Count to 100');return false;">Count to 100</button>
<div id="workerComms">Things workers say...</div>
setInterval(postMessage(i + 1), 1000); calls postMessage(i + 1) and then passes the return value into setInterval, exactly the way foo(bar()) calls bar and passes the return value into foo.
Instead:
You want to pass a function reference to setInterval
You want to use setTimeout, not setInterval
You want to vary the timeout, because otherwise they'll all happen stacked on top of each other one second later
Something like:
for (i = 1; i <= 100; i++) {
setTimeout(postMessage.bind(window, i), 1000 * i);
}
would probably do it. That schedules 100 timers, at one-second intervals. It uses postMessage.bind(window, i) to create a function that, when called, will all postMessage with this set to window and passing in i as the first argument. I did i from 1 to 100 rather than 0 to 99 to avoid having to add 1 to it in both places I used it.
Alternately, you could ditch the for loop entirely and use setInterval or a chained series of setTimeout. Here's the setInterval:
var i = 0;
var timer = setInterval(function() {
postMessage(++i);
if (i >= 100) {
clearInterval(timer);
}
}, 1000);

Problem with setTimeout()

This is my code. What I want it to do is write 0, wait one sec, write 1, wait one sec, write 2, wait one sec, etc. Instead it writes 5 5 5 5 5
for(i = 0; i < 5; i++) {
setTimeout("document.write(i + ' ')", 1000);
}
http://jsfiddle.net/Xb7Eb/
1) You set all the timeouts to last 1 second at the same time. The loop doesn't wait for the timeout to occur. So you have 5 timeouts that all execute at the same time.
2) When the timeouts execute, the loop is long since complete and i has become 5. So once they execute, they all print "5"
3) document.write() writes somthing onto the page, in the same place it executes. I.e. if you have <script>document.write("xyz")</script> in the middle of a piece of text, it'll write "xyz" in the middle of the text. The timeouts, however, are not necessarily anywhere on the page. They exist only in code.
Here's a solution that's as close to yours as possible: http://jsfiddle.net/rvbtU/1/
var container = document.getElementById("counter");
for(i = 0; i < 5; i++) {
setTimeout("container.innerHTML += '" + i + " ';", 1000 * i);
}
However, that solution uses setTimeout's ability to evaluate a string as javascript, which is never a good idea.
Here's a solution that uses an anymous function instead: http://jsfiddle.net/YbPVX/1/
var container = document.getElementById("counter");
var writer = function(number) {
return function() { container.innerHTML += String(number) + " "; };
}
for(i = 0; i < 5; i++) {
setTimeout(writer(i), 1000 * i);
}
Edit: Forgot to save the 2nd fiddle. Whoops. Fixed now.
Most of the answers available are giving bad advice.* Specifically, you shouldn't be passing a string to setTimeout anymore (it still works, but it's discouraged), it's no longer 2000, there are better ways to do this.
setTimeout takes a function as the first parameter, and that's what you should do, however there are some issues when calling setTimeout in a loop.
This looks like it should work:
var i;
for ( i = 0; i < 5; i++ )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i + 1) );
}
But it doesn't. The issue is that by the time setTimeout executes the function, the loop will have incremented i to 5, so you'll get the same value repeated.
There are a few fixes. If you're willing to risk a with statement, you could try the following:
var i;
for ( i = 0; i < 5; i++ )
{
with( { i:i } )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
}
}
Note that with is typically discouraged just like passing string values to setTimeout, so I don't really suggest this method of doing things.
The better way is to use a closure:
var i;
for ( i = 0; i < 5; i++ )
{
(function(i){
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
})(i);
}
To explain what's going on, the anonymous function wrapper (function(i){...code...}) executes immediately because it's wrapped in parens and passed i as a value:
(function(i){...code...})(i);
This forces the i variable that document.write uses to be a different one than what's being used in the for loop. You could even change the parameter used in the anonymous function wrapper if the difference gets too confusing:
(function(a){document.write(a+' ')})(i);
* when I started writing this question there were a number of answers describing how to fix the string to work with setTimeout, although they would technically work, they didn't include why they would work (because 'document.write("' + i + ' ");' evaluates i at the time of calling due to string concatenation, versus evaluating i at runtime like the previous version did), and they most certainly didn't mention that it's the bad old way of calling setTimeout.
try
var i = 1;
function timeout(){
document.write(i + ' ');
i++;
if (i == 5) return;
setTimeout(timeout, 1000);
}
timeout();
http://jsfiddle.net/nnJcG/1/
You have a problem with clousures, you can try this:
var timeout = function(){
var i = 0;
return function(){
document.write(i+ ' ');
i++;
if(i!==5)
setTimeout(timeout,1000);
};
}();
setTimeout(timeout,1000);
Here is the example in jsBin http://jsbin.com/uloyuc/edit
First of all, NEVER pass a string to setTimeout. Use a function, it's much cleaner.
Second, you have to "close over" the loop value. I bet this is what you want.
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
document.write(i + ' ')
}, i * 1000);
}(i));
}
See more about you a self executing function to close over a loop value here http://www.mennovanslooten.nl/blog/post/62
And just cause I love it, here is the equivalent in CoffeeScript whihc has the do keyword to help out with just this case.
for i in [0..4]
do (i) ->
setTimeout ->
document.write "#{ i } "
, i * 1000
You can also work with setInterval and clearInterval:
var i = 0;
var f = setInterval(function() {
if(i == 4) clearInterval(f);
document.write(++i + ' ');
}, 1000);
I think this code is very readable.
You could try like this:
var tick_limit = 5; // Or any number you wish representing the number of ticks
var counter = 0; // Or any number you wish
var timer_interval = 1000; // Interval for the counter
var timer;
function timerTick()
{
if(counter < tick_limit)
{
// Execute code and increase current count
document.body.innerHTML+=(counter + ' '); // Append the counter value to the body of the HTML page
counter++;
timer = setTimeout(timerTick,timer_interval);
}
else
{
// Reset everything
clearTimeout(timer);
counter = 0;
}
}
function startCounter()
{
clearTimeout(timer); // Stop current timer
timer = setTimeout(timerTick,timer_interval); // Start timer with any interval you wish
}
...
// Start timer when required
startCounter();
...
This way, calling the startCounter a number of times will result in a single timer executing the code
You're triggering five timeouts at the same time.
I like Pindatjuh's answer, but here's another fun way to do it.
This way starts the next timeout when the previous one is finished:
// Wrap everything in a self executing anonymous function so we don't pollute
// the global namespace.
//
// Note: Always use "var" statments or you will pollute the global namespace!
// For example "for(i = 0; i < 5; i++)" will pollute the global namespace
// unless you have "var i; for(i = 0; i < 5; i++)" or
// "for(var i = 0; i < 5; i++)" & all of that is not in the global namespace.
//
(function() {
// "i" will be available within doThis()
// you could also pass "i" as an argument
var i = 0,
doThis = function() {
// setTimeout can take an anonymous function
// or a regular function. This is better than
// eval-ing a string.
setTimeout(function() {
document.write(i + ' ');
++i;
// Do the function again if necessary
if (i < 5) doThis();
}, 1000);
}
// Let's begin!
doThis();
})();
Working Example

Categories