Why is this javascript not running as expected? - javascript

function animateGraph() {
var graph;
for(i=0; i<10; i++)
{
var start = new Date();
while((new Date()) - start <= 500) {/*wait*/}
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
}
}
The loop works. The wait works. But the document.getElement is not showing up until the last item in the array...why?

Using setTimeout will allow the code to run and not lock up the page. This will allow it to run the code and will not effect other elements on the page.
var cnt = 0;
(function animateGraph() {
document.getElementById("timeMark").innerHTML = phoneX[cnt].epoch;
cnt++;
if (cnt<10){
window.setTimeout(animateGraph,500);
}
})();

The while loop, waiting for a datetime, is not a good way to wait - it just blocks execution. It keeps the browser (including UI, and its updating) frozen until the script finishes. After that, the window is repainted according to the DOM.
Use window.setTimeout() instead:
function animateGraph(phoneX) {
var el = document.getElementById("timeMark")
var i = 0;
(function nextStep() {
if (i < phoneX.length )
el.innerHTML = phoneX[i].epoch;
i++;
if (i < phoneX.length )
window.setTimeout(nextStep, 500);
})();
}
Please note that this runs asynchronous, i.e. the function animateGraph will return before all phoneXes are shown.

Use setTimeout instead of a while loop.
https://developer.mozilla.org/en/DOM/window.setTimeout
Also try something like this.
Javascript setTimeout function

The following snippet uses a helper function to create the timers. This helper function accepts a loop counter argument i and calls itself at the end of the timer handler for the next iteration.
function animateGraph() {
var graph;
setTimeMarkDelayed(0);
function setTimeMarkDelayed(i) {
setTimeout(function() {
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
if (i < 10) {
setTimeMarkDelayed(++i);
}
}, 3000);
}
}
You actually need some sort of helper function, otherwise you'll end up overwriting the value of i in your for loop in every iteration and by the time your timers run out, i will already be 9 and all handlers will act on the last element in phoneX. By passing i as an argument to the helper function, the value is stored in the local scope of that function and won't get overwritten.
Or you could use setInterval like Radu suggested, both approaches will work.

Related

loop and setTimeout function still not working after some research

I did some research about loops and setTimeout function, but still its not working as I wish...
It opens all the links in same time without delay of 5 seconds per link that opened.
I would like it to open every link with 5 second delay after every each of them.
Code:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]')
for (var i = 1; i <= links.length; i++) {
(function(index) {
setTimeout(function() {
window.open(links[index].href,'_blank');
}, 5000);
})(i);
}
Using a Promise chain and Array#reduce(), you can do this:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
Array.from(links).reduce((chain, { href }) => {
return chain.then(() => new Promise(resolve => {
window.open(href, '_blank');
setTimeout(resolve, 5000);
}));
}, Promise.resolve())
If you don't want to do something quite this fancy and you're fine setting all your timeouts at once, you can simplify your for loop using let instead of var and an IIFE:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
for (let i = 0; i < links.length; i++) {
setTimeout(function() {
window.open(links[i].href, '_blank');
}, 5000 * i);
}
Or even simpler, using for...of and object destructuring:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
var i = 0;
for (const { href } of links) {
setTimeout(function() {
window.open(href, '_blank');
}, 5000 * i++);
}
That's because all your timeouts are set immediately, almost at one time. So the ends of timeouts are take place almost on the same time. Try this:
var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]')
for (var i = 1; i <= links.length; i++) {
(function(index) {
setTimeout(function() {
window .open(links[index].href,'_blank');
}, 5000 * i);
})(i);
}
setTimeout is asyncronous. I would solve this specified problem with:
Array.from(links).reduce((a,e)=>{
setTimeout(function() { window .open(e.href,'_blank');}, a);
return a + 5000;
} ,5000);
From the code above i assume that you wish to open the links once every 5 seconds so here are the improvements and suggestion made upon your code
// use JS hoisting for variable declarations
var
// Timer to store the next timer function reference
timer,
// The delay between each function call
delay = 5000,
// Set the desired selector
selectors = 'a[class="mn-person-info__link ember-view"][id^="ember"]',
// Get the list of the required selectors.
links = document.querySelectorAll(selectors);
// Create a function which will be called every X seconds
function openLink( index ){
// validate that the index is not out of bound
if (index === links.length){
return;
}
// get the current link and open new window with the link url
window.open(links[index].href,'_blank');
// Set the next timer to open the next window
timer = setTimeout( openLink, delay, ++index);
}
// Call the function for the first time with index = 0
openLink( 0 );
What does this code do?
The first section is declaration of the variables which will be used in this script. The preferred way to declare variables id to to use hoisting
Hoisting
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.
Timers
If you wish to open the links in a sequence you should put them inside a function which will call them one after the other instead of using the for loop. The for loop will place all of them in the call stack/event loop and all of them will be executed after 5000 milliseconds since this is what set time out will so, it will schedule the execution of the code to 5000 milliseconds from now for all of them.
Im recommending you to watch the amazing lecture by Philip Roberts
Philip Roberts: What the heck is the event loop anyway
Saving the return value from the setTimeout will allow you to later on cancel the timer if you wish using the clearTimeout( timer )
Summary
Since you had a for loop it will simply loop over all your links. The setTimeout will set the scheduled execution time 5 seconds from now for all the links.
The trick here is to set the next timer after the current one is opened. This is why the sertTimeout is defined within the function itself.
The setTimeout gets a third param which is the parameter passed to the function when its being called.
Easy way:
links.forEach(function(i, link) {
setTimeout(function() {
window.open(link.href,'_blank');
}, 5000 * i);
});
Just wait i * 5 seconds, where i is the index of the link

Placing timer ID into its function

I would like to put timer ID, returned by setInterval(), into its function:
delay_timeout = setInterval(function () {
test_delay(data['time'], delay_timeout);
}, 1000);
Is it possible? To my mind delay_timeout doesn't have a value at this point...
I don't want to save delay_timeout globally for using later in timer's function to stop it. Several timers may work at the same time.
UPDATE:
Code is not global, it is located here:
socket.on('test_delay', function (data) {
...
});
The point is not to make delay_timeout global and be able to kill timer by some condition within its callback function.
your code works fine if you put the setTimeout call in it's own function like this:
function setTimer(){
var timeId = setTimeout(function(){ console.log(timeId); }, 1);
}
for(var i = 0; i < 10; i ++){
setTimer();
}
here's a fiddle
The delay_timeout variable is available to your callback as it is in the same enclosure.
So long as you are not in the same context and rerun your setTimeout before it has triggered the callback you will be fine. so this won't work:
var timeId;
for(var i = 0; i < 10; i ++){
timeId = setTimeout(function(){ console.log(timeId); }, 1);
}
(see the second half of the fiddle...)

Add delay between script execution, especially in FOR loop

I have a block of script that will produce a set of DOM. It has a loop that contains an asynchronous function (function A) and then function B after it. Function B's effect will be shown if the asynchronous function (A) is completely executed. I tried to use setTimeout function, but it doesn't work well, some of object outside setTimeout could not be accessed inside it.
Here's the script:
var parents = data.hasil.parent.split(">>");
var jmlparent = parents.length;
for (var i = jmlparent-2; i >= 1; i--){
add_parent(); // it's the asynchronous function (function A)
setTimeout(function (){
$("#parent_"+(jmlparent-i)).val(parents[(i-1)]); //(function B)
//returns error: parents is not defined
},200);
//i think 200ms is enough for browser to complete the execution of function A
}
Does anyone have the solution?
Function A should return a promise which when resolved you would call function B. You do not know how long function will take to resolve, therefore setTimeout() is not a good approach.
You also do need a closure:
var parents = data.hasil.parent.split(">>");
var jmlparent = parents.length;
for (var i = jmlparent-2; i >= 1; i--){
(function( i ) {
add_parent().done(function() {
$("#parent_"+(jmlparent-i)).val(parents[(i-1)]);
});
})( i );
}
I did not want to discuss the use of setTimeout but just improve you code. Try this :
...
for (var i = jmlparent-2; i >= 1; i--){
add_parent();
setTimeout(function (a){
$("#parent_"+(jmlparent-a)).val(parents[(a-1)]); //(function B)
},200, i); // we pass here the i var to setTiemout
}
About add_parent() why you call it inside the loop ? may be before the loop once is better ?! I dont know ...

Why setInterval wont stop?

Why console.log(1) gets executed here forever:
var interval = setInterval(function() {
if (true) {
clearInterval(interval);
console.log(1);
}
}, 100);
It depends on the scope within which you're executing this code.
If interval is unique within its scope — be it global or function scope — then this will work as expected.
If, however, you execute this code within a loop (for example), then you are overwriting interval with some new interval on each iteration, breaking your clearInterval call for all but the very last setInterval call:
for (var i = 0; i < 3; i++) {
var interval = setInterval(function() {
if (true) {
clearInterval(interval);
console.log(1);
}
}, 100);
}
// ^ will give you one single console log entry,
// and two more console log entries per second forever
It's seems that your variable interval is used somewhere again. If I run code you provided it works as expected. I guess user Lightness has given a great explaination of this, also he provided piece of code where "closure problem" is obvious (which caused you problem too). I just want to add extra information. If you want your code inside of loop + setInteval works aparat you can do the following:
for (var i = 0; i < 3; i++) {
var o = {
i: i,
interval: null,
timer: function() {
if (true) {
clearInterval(this.interval);
console.log(this.i);
}
}
};
o.interval = setInterval(o.timer.bind(o), 1000);
}
DEMO
I hope it will be useful for someone.

Stopping .each execution on every iteration for a spesific time interval in jquery?

I am trying to create the following functionality in my javascript:
$("mySelector").each(function(){
// Do something (e.g. change div class attribute)
// call to MyFunction(), the iteration will stop here as long as it will take for myFunction to complete
});
function myFunction()
{
// Do something for e.g. 5 seconds
}
My question is how can I stop every iteration for the duration of the myFunction()?
No, that isnt possible. You'll have to code it differently, possibly with a setTimeout based on the current index of .each.
$("mySelector").each(function(i){
// Do something (e.g. change div class attribute)
// call to MyFunction(), the iteration will stop here as long as it will take for myFunction to complete
setTimeout(myFunction,i*5000);
});
function myFunction()
{
// Do something for e.g. 5 seconds
}
Edit: You can also do it with queuing: http://jsfiddle.net/9Bm9p/6/
$(document).ready(function () {
var divs = $(".test");
var queue = $("<div />");
divs.each(function(){
var _this = this;
queue.queue(function(next) {
myFunction.call(_this,next);
});
});
});
function myFunction(next) {
// do stuff
$(this).doSomething();
// simulate asynchronous event
var self = this;
setTimeout(function(){
console.log(self.id);
// go to next item in the queue
next();
},2000);
}
​
Here's a jsFiddle that I think will do what you need:
http://jsfiddle.net/9Bm9p/2/
You would just need to replace the selector with what you use.
The "loop" that is occurring will wait for myFunction to finish before moving on to the next element. I added the setTimeout inside of myFunction to simulate it taking a period of time. If you are using asynchronous things, such as an AJAX request, you would need to put the call to myFunction inside of the complete method...or in the callback of an animation.
But as someone already commented, if everything in myFunction is synchronous, you should be able to use it as you are. If you are looking for this process to be asynchronous, or if things in myFunction are asynchronous, you cannot use a for loop or .each().
(function () {
"use strict";
var step = 0;
var content = $("mySelector");
var max = content.length;
var speed = 5000; // ms
var handle = setInterval(function () {
step++;
if (step >= max) {
clearInterval(handle);
} else {
var item = content[step];
// do something
}
}, speed);
}());
setInterval will do it once-every-n-miliseconds, and clearInterval will stop it when you're done. This won't lock up the browser (provided your "do something" also doesn't). FRAGILE: it assumes that the results of $("mySelector") are valid for the duration of the task. If that isn't the case then inside do something then validate item again.

Categories