setTimeout not pausing iterations in loop - javascript

I am trying to iterate through a list of orders, pausing each one for 3 seconds. Then once complete, start again in a loop.
In the snippet below, if the loop is enabled it seems to be starting the loop again before the first loop has completed.
Also the 3 second pause between each call to display_notification is not working.
start: function() {
$.each(this.orders, function(index, order) {
setTimeout(function() {
console.log ('show notification');
recentlyApp.display_notification(order);
}, index * 3000);
});
if (this.settings.loop) {
this.start();
}
},
display_notification: function(order) {
console.log(order);
}

As soon as you're already using jQuery, you can create a plugin to loop through an array in an interval.
Example:
// timer function that loops through an array with in a given interval
$.timer = function (list, callback, time/*, onFinish, index*/) {
var onFinish = arguments.length > 3 ? arguments[3] : void 0,
index = arguments.length > 4 ? arguments[4] : 0;
if (index < list.length) {
list.__timed = setTimeout(function() {
callback.call(this, index, list[index]);
$.timer(list, callback, time, onFinish, ++index);
}, time);
}
else if (onFinish){
onFinish.call(this);
}
return {
cancel: function() {
if (list.__timed !== void 0) {
clearTimeout(list.__timed);
delete list.__timed;
}
}
};
}
// usage
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var timer = $.timer(arr, function (index, item) {
document.querySelector('div').insertAdjacentHTML('beforeend', '<div>' + item + '</div>');
}, 3000, function() {
document.querySelector('div').insertAdjacentHTML('beforeend', '<div>Finished</div>');
});
// cancelling the loop
$('button').click(function(e) {
timer.cancel();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>
<button>Cancel timer</button>
So, in your scenario, it would be something like this:
start: function() {
$.timer(this.orders, function(index, order) {
console.log('show notification');
recentlyApp.display_notification(order);
}, 3000, this.settings.loop ? this.start : void 0);
},
display_notification: function(order) {
console.log(order);
}
UPDATE
Now, the $.timer is returning an object with the cancel method, so you can cancel the loop at anytime by calling .cancel();.
I've created a __timed property into the array to always get the last setTimeout executed, so when you call the cancel method, it calls the clearTimeout for what it was going to call next.

You're calling this.start() as soon as you've finished the loop that sets up the timeout functions.
Either track the last value of index and use it to set a timeout on the call to start() or put the call to start() in the last timed out function you set going in the loop.

Related

Complete all functions inside a FOR LOOP before iterating to next iteration of the loop

Say i have a function like :
var bigArray = [1,2,3,4,5.........n];
for(var i=0; i<bigArray.length; i++){
if(someCondition){
setTimeout(function(){
..do_stuff..
callSomeFunction();
}, someDelayTime);
}
if(someCondition){
setTimeout(function(){
..do_stuff..
callSomeFunction();
}, someDelayTime);
}
if(someCondition){
setTimeout(function(){
..do_stuff..
callSomeFunction();
}, someDelayTime);
}
.
.
.
.
.
.
.
.
if(someCondition){
setTimeout(function(){
..do_stuff..
callSomeFunction();
}, someDelayTime);
}
}
Now what i want from this function is that when all of the conditions have performed their intended task, only then it should move to the next iteration of FOR loop. In other words, i=0 in FOR LOOP should change to i=1 and so-on iff all of the conditions inside the FOR loop have completed their job in current iteration.
Currently behaviour of this code is very random (because of setTimeout i believe). How can i make this work as per my expectations?
I recently read about promises (don't know much about them) but i'm not sure how to implement them in this code or if they will work in this case...
The original idea came from the answer of Nina Scholz on some similar question.
this answer is good if you don't like promises and deferred object. Othewise Kris Kowal's Q library would be a better choice.
function Iterator() {
var iterator = this;
this.queue = [];
this.add = function(callback, wait) {
iterator.queue.push(iterator.wait(wait));
iterator.queue.push(function() {
callback();
iterator.next();
});
};
this.wait = function(wait) {
return function() {
setTimeout(iterator.next, wait);
};
};
this.next = function() {
iterator.queue.length && iterator.queue.shift()();
};
}
var arr = [1, 2, 3, 4, 5],
counter = -1;
var iterator = new Iterator();
(function fillNextIteration() {
if (counter >= arr.length)
return;
counter++;
iterator.add(function() {
console.log('1 - counter value is ' + counter);
}, 100);
iterator.add(function() {
console.log('2 - counter value is ' + counter);
}, 100);
iterator.add(function() {
console.log('3 - counter value is ' + counter);
}, 100);
iterator.add(function() {
console.log('4 - counter value is ' + counter);
}, 100);
iterator.add(fillNextIteration, 100);
iterator.next();
})();
Code Explanation
Iterator class has one queue which is and array and some methods:
next
when you call next() if there is any callback in queue it will fetch first and execute that. when you call Array#shift it removes and returns first item from array. When this item is a function then you can invoke it by adding parentheses in front of it. here is the expanded version:
if (iterator.queue.length > 0) {
var callback = iterator.queue.shift();
callback();
}
wait
This method will add an extra callback to queue which after a timeout calls the next method. this one is for creating the expected delay.
add
This method calls the wait with desired delay then attaches another function to queue which will call the callback and then calls next to make callback chain running.
fillNextIteration
after explaining the iterator there is another loop here on function fillNextIteration this starts with the conditions for the first iteration for example:
if (someConditionIsTrue) {
iterator.add(function() {
doWhatShallBeDone;
}, delay);
}
you can check all conditions and then if required add the callback as shown.
after all condition finished add fillNextIteration as last action to continue the loop.
self invoking function
As you can see fillNextIteration is self invoked.
This helps to reduce one invoke and works like this:
function f(){};
f();
Last thing to mention
you can create a loop by calling a function inside itself. if there is no delay or stop for revoke then it would be a deadlock. So this is another loop as well:
(function loop() { setTimeout(loop, delay); })();
Try this please :
bigArray = [1, 2, 3, 4, 5.........n];
neededStuff = 10; // Number of finished works you need below
stuffOK = neededStuff;
function mainFunction(i) {
function loopFunction(i) {
if (someCondition) {
setTimeout(function () {
// .. do_stuff..
stuffOK++;
callSomeFunction();
}, someDelayTime);
}
if (someCondition) {
setTimeout(function () {
// .. do_stuff..
stuffOK++;
callSomeFunction();
}, someDelayTime);
}
if (someCondition) {
setTimeout(function () {
// .. do_stuff..
stuffOK++;
callSomeFunction();
}, someDelayTime);
}
/*
...
*/
if(someCondition) {
setTimeout(function () {
// .. do_stuff..
stuffOK++;
callSomeFunction();
}, someDelayTime);
}
}
if (stuffOK == neededStuff) {
i++;
stuffOK = 0; // reinit counter
loopFunction(i);
}
setTimeout('mainFunction(' + i + ');', 100);
}
mainFunction(-1);
Hope this will work from promise chaning
callSomeFunction1().then(function() {
callSomeFunction2();
}).then(function() {
callSomeFunction3();
})

Javascript async call

I am having trouble with Javascript async call. The Car class below has a move function which takes two arguments, the first one is a value, the second one is a call back function, which will be called after 1 second, and this callback function takes the value returned by forward method.
var Car = function() {
this._count = 0;
};
Car.prototype = {
move: function(value, onMoved) {
setTimeout(function() {
onMoved(this.forward(value));
}.bind(this), 1000);
},
forward: function(value) {
this._count = this._count + value;
return this._count;
}
};
I want to call the move function like this:
var values = [1, 2, 3, 4];
var car = new Car();
values.forEach(function(value) {
car.move(value, function(result) {
console.log(result);
});
});
Now my problem is the call back function onMoved does not wait for 1 second to execute between each value that it is outputting. How can I make it so it wait between each value that it is outputting? I am allowed to use underscore.js. Thanks.
setTimeout in javascript registers callback function in a queue to execute in future once the current execution stack is free. for example:-
while(1){
setTimeout(function(){
console.log('hello');
},1000);
}
this will not print hello as the execution stack will never be free.
Coming back to the example we call move method which will be pushed to a queue. After one second it starts executing each function one by one without any delay as the setTimeout is set to a fixed time ie. 1000 millisecond.
workaround:-
var Car = function() {
this._count = 0;
};
Car.statCount = 0;
Car.prototype = {
move: function(value, onMoved) {
this.constructor.statCount++;
setTimeout(function() {
onMoved(this.forward(value));
}.bind(this), 1000*Car.statCount);
},
forward: function(value) {
this._count = this._count + value;
return this._count;
},
constructor: Car
};
var values = [1, 2, 3, 4];
var car = new Car();
values.forEach(function(value) {
car.move(value, function(result) {
console.log(result);
});
});
Since you want a 1 sec. wait between each call, you might consider using the setInterval method on window, instead of utilizing the setTimeout method:
You could add some method performing some actions for each iteration, and finish iterating when all values are processed:
var i=0;
function go(result) {
if (!!values[i]) {
car.move(values[i], function(result) {console.log(result);})
i++;
} else {
window.clearInterval(interval);
}
}
And call this function using setInterval
var interval = window.setInterval(go, 1000);
Have a look at the following fiddle:
https://jsfiddle.net/adw1k98m/1/
(see console log for output)

Make a timer using setInterval()

I'm trying to make a timer in javascirpt and jQuery using the setInterval function.
The timer should count down from 90 to zero (seconds).
The code that I'm using for this:
setInterval(settime(), 1000);
in this settime() sets the var time (started on 90) -1, this action has to happen once every second.
My problem is that I don't get how to let this action happen 90 times; I tried using a for loop but then the counter counts from 90 to 0 in 1 second.
Does anyone knows a better alternative?
Something like this should do the trick:
var count = 90;
var interval = setInterval(function(){
setTime();
if (count === 0){
clearInterval(interval); // Stopping the counter when reaching 0.
}
}, 1000);
I don't have the code you need but I'm sure you'll need to update the count at some point on your page.
You can cancel an interval with clearInterval which needs the ID of the interval that's created when you call setInterval
function timer(seconds, cb) {
var remaningTime = seconds;
window.setTimeout(function() {
cb();
console.log(remaningTime);
if (remaningTime > 0) {
timer(remaningTime - 1, cb);
}
}, 1000);
}
var callback = function() {
console.log('callback');
};
timer(90, callback);
Caution in using setInterval, may not work as you expect http://bonsaiden.github.io/JavaScript-Garden/#other.timeouts
setInterval keeps calling your function at each second (since you use 1000).
So setInterval expects a function as its first argument, which is the function which will be called periodically. But instead of passing settime, you pass its returned value. That won't work, unless settime returns a function.
Instead, try
setInterval(settime, 1e3);
Try utilizing .data() , .queue() , animate() , .promise() ; to stop "countdown" can call $(element).clearQueue("countdown")
var counter = function counter(el) {
return $(el || window).data({
"start": {
"count": 0
},
"stop": {
"count": 1
},
"countdown": $.map(Array(90), function(val, key) {
return key
}).reverse(),
"res": null
})
.queue("countdown", $.map($(this).data("countdown")
, function(now, index) {
return function(next) {
var el = $(this);
$($(this).data("start"))
.animate($(this).data("stop"), 1000, function() {
el.data("res", now)
$("pre").text(el.data("res"));
next()
});
}
})
)
.promise("countdown")
.then(function() {
$("pre").text($(this).data("res"))
.prevAll("span").text("countdown complete, count:");
});
};
$("button").on("click", function() {
if ($(this).is("#start")) {
counter();
$("pre").text(90).prev("span").html("");
$(window).dequeue("countdown");
}
else {
$(window).clearQueue("countdown").promise("countdown")
.then(function() {
$("pre").prevAll("span").html(function(_, html) {
return html.replace("complete", "stopped")
});
})
}
});
pre {
font-size:36px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<button id="start">start</button><button id="stop">stop</button>
<br />
<span></span>
<br />
<pre>90</pre>

Applying delay between each iteration of jQuery's .each() method

Having some problems with the following block of code:
$('.merge').each(function(index) {
var mergeEl = $(this);
setTimeout(function() {
self.mergeOne(mergeEl, self, index - (length - 1));
}, 500);
});
I'm trying to apply a .500 second delay between each call of mergeOne, but this code only applies a .500 second delay before calling mergeOne on all the elements in the array simultaneously.
If someone could explain why this code doesn't work and possibly a working solution that would be awesome, thanks!
Here's a general function you can use to iterate through a jQuery object's collection, with a delay between each iteration:
function delayedEach($els, timeout, callback, continuous) {
var iterator;
iterator = function (index) {
var cur;
if (index >= $els.length) {
if (!continuous) {
return;
}
index = 0;
}
cur = $els[index];
callback.call(cur, index, cur);
setTimeout(function () {
iterator(++index);
}, timeout);
};
iterator(0);
}
DEMO: http://jsfiddle.net/7Ra9K/ (loop through once)
DEMO: http://jsfiddle.net/42tXp/ (continuous looping)
The context and arguments passed to your callback should be the same as how .each() does it.
If you want to make it a jQuery plugin, so it can be called like $("selector").delayedEach(5000, func..., then you could use this:
$.fn.delayedEach = function (timeout, callback, continuous) {
var $els, iterator;
$els = this;
iterator = function (index) {
var cur;
if (index >= $els.length) {
if (!continuous) {
return;
}
index = 0;
}
cur = $els[index];
callback.call(cur, index, cur);
setTimeout(function () {
iterator(++index);
}, timeout);
};
iterator(0);
};
DEMO: http://jsfiddle.net/VGH25/ (loop through once)
DEMO: http://jsfiddle.net/NYdp7/ (continuous looping)
UPDATE
I added the ability to continuously loop through the elements, as an extra parameter. Passing true will continuously loop, while passing false or nothing (or something falsey) will only loop over the elements once. The code and fiddles include the changes.

Delay JavaScript's function execution

I have a JQuery's .each loop that calls a function with a parameter per iteration, is there a way to delay this function call? I have tried setTimeout as in the following but this does not work as the function gets executed immediately.
$.each(myArray, function (j, dataitem)
{
setTimeout(function () { showDetails(dataitem) }, 300);
});
function showDetails(dataitem)
{
...
}
Array size is roughly 20, What I'm trying to do is to distribute function calls over a certain time frame instead of immediately, any idea how to achieve this? I'm prepared to rewrite and restructure how functions get called to get this done, any help would be appreciated.
You could use the index of the array to calculate the interval dynamically:
$.each(myArray, function (j, dataitem) {
window.setTimeout(function () {
showDetails(dataitem)
}, (j + 1) * 300);
});
You execute them all after 300 milliseconds. Instead, try something like this:
window.setTimeout(function () { showDetails(dataitem) }, (j + 1) * 300);
Edit: instead of creating 20 timers at once I think it's better to do it one by one. Function should be:
function showDetails(index)
{
if (index >= myArray.length)
return false;
var dataItem = myArray[index];
//code here......
//code here......
//code here......
windows.setTimeout(function() { showDetails(index + 1); }, 300);
}
And first call can be:
$(document).ready(function() {
{
showDetails(0);
});
This assume myArray is plain global array, and will handle one item and only then call the next item with delay.
Take a look at jQuery.queue([ queueName ], callback( next )). This allows you to queue functions up to be called and is what jQuery's animation effects use internally.
It sounds like you would like to implement a queue, although it is not entirely clear you intentions for doing so.
EDIT: re-reading your question, I think other answers better match what you are after, however I thought that I would show you an example of how to achieve delayed function execution with a custom queue.
An example of how you could use a queue.
var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
output = $('#output');
// set the queue up
$.each(myArray, function (j, dataitem) {
output.queue('queue', function(next) {
var that = this;
showDetails(dataitem);
window.setTimeout(next,300);
});
});
// start the queue running.
output.dequeue('queue');
function showDetails(dataitem) {
output.append('<div>' + dataitem + '</div>');
}
Just don't use $.each, but something like:
var data = [1, 2, 3, 4, 5];
function showDetails(values, delay) {
console.log(values.shift()); //show the value
if (values.length) {
setTimeout(function() {showDetails(values, delay); }, delay); //schedule next elem
}
}
showDetails(data.slice(0), 300); //dont forget the slice, call-by-reference

Categories

Resources