Related
I have a list of events with timestamp.
What I want is to display the events based on the timestamp:
To add a delay:
delay = timestamp(t+1) - timstamp(t)
I know this doesn't work well with setTimeout, but there is an workaround, if the timeout is constant, in my case is not.
Is it possible to make the next setTimeout() wait for the previous one? To be specific, if the first setTimeout() has a 5 second delay and the second one has 3 seconds, the second one will appear first. I want them to be in the same order but execute one after the other.
This example works for a constant delay, but I want to calculate the delay based on the information I take iterating the list.
for (i = 1; i <= 5; ++i) {
setDelay(i);
}
function setDelay(i) {
setTimeout(function(){
console.log(i);
}, 1000);
}
You can use IIFE (Immediately Invoked Function Expression) and function recursion instead. Like this:
let i = 0;
(function repeat(){
if (++i > 5) return;
setTimeout(function(){
console.log("Iteration: " + i);
repeat();
}, 5000);
})();
Live fiddle here.
When using the latest Typescript or ES code, we can use aync/await for this.
let timestampDiffs = [3, 2, 4];
(async () => {
for (let item of timestampDiffs) {
await timeout(item * 1000);
console.log('waited: ' + item);
}
})();
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
We need to wrap the for loop in an async immediately invoked function because to support await.
We also do need a timeout function that returns a promise once the timeout is complete.
After that, we wait for the timeout to complete before continuing with the loop.
Don't call it within a loop, as it won't wait for the setTimeout to complete.
You can instead pass a list of times that you want to wait, and iterate them recursively:
let waits = [5000, 3000, 1000];
function setDelay(times) {
if (times.length > 0) {
// Remove the first time from the array
let wait = times.shift();
console.log("Waiting", wait);
// Wait for the given amount of time
setTimeout(() => {
console.log("Waited ", wait);
// Call the setDelay function again with the remaining times
setDelay(times);
}, wait);
}
}
setDelay(waits);
You can set time out period using i value dynamically. Like
for (i = 1; i <= 5; ++i) {
setDelay(i);
}
function setDelay(i) {
setTimeout(function(){
console.log(i);
}, i*1000);
}
You can think of something like that:
setDelay(1,5);
function setDelay(i, max) {
setTimeout(function(){
console.log(i);
if(i < max){
i++;
setDelay(i, max);
}
}, 1000);
}
The idea is to set the next setTimeout(.. in a timeout.
Of course you can modify it to have different long timeouts as well.
Hope i can help :)
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
Look at the following code:
var timer=setTimeout(function(){increase();}, 8);
This setTimeout function will be executed immediately, but I want
it to execute later. Why?
Example:
function increase(i)
{
i++; if(i==100) return;
var timer=setTimeout(function(){increase(i);}, 8);
}
Now, I need to stop and exit this function within another function when certain thing happen:
if (a==b) clearTimeout(timer);
The thing that bothers me is that variable timer is getting assigned, whenever
function increase runs, but it does not need to, and I believe it is bad practice. That is why I need to assign to it only once, before function run and execute it later when need arrives.
I hope you understood, and btw, those are just examples, not my code.
You can declare a variable outside of function the calls setTimeout, define the variable as setTimeout when the function is called; call clearTimeout() from another function with variable referencing setTimeout as parameter.
var timer = null, // declare `timer` variable
n = 0, // reference for `i` inside of `increase`
i = 0,
a = 50,
b = 50,
// pass `increase` to `t`, call `increase` at `setTimeout`
t = function(fn, i) {
// define timer
timer = setTimeout(function() {
fn(i)
}, 8)
};
function increase(i) {
console.log(i);
// set `n` to current value of `i` to access `i`:`n`
// to access `i` value outside of `t`, `increase` functions
n = i++;
if (i == 100) return;
t(increase, i); // call `t`
}
increase(i);
// do stuff outside of `t`, `increase`
setTimeout(function() {
// clear `timer` after `200ms` if `a == b`
if (a == b) {clearTimeout(timer)};
alert(n)
}, 200)
If you want the operation of one function to change the conditions of another, just declare a boolean variable within the scope of both functions and change it's value depending on a terminator function.
For example, take a look at this code:
var exit = false;
function increase(i) {
if(i==100 || exit) return;
setTimeout(function(){ increase(++i) }, 1000);
}
function terminator(a, b){
exit = (a==b);
}
increase(0);
Here, if terminator is ever called with a pair of equal arguments like:
setTimeout(function(){ terminator(true, 1) }, 5000) // timeout of 5 seconds means increase will run 5 times
the recursive call of setTimeout within the increase function will not be reached (after 5 seconds), as the function will return before reaching that line of code.
If terminator is never called, or called with unequal arguments like:
setTimeout(function(){ terminator(true, false) }, 5000) // using setTimeout here is just arbitrary, for consistency's sake
increase will only time out once it's completed 100 recursions (in other words, after 100 seconds have elapsed)
Hope this helps!
Because the delay in setTimeout takes millisecond as time unit, so in your code, you set your function to be executed after 8ms, which feels like immediately.
function increase(i, boolean) {
i++;
if (i == 100) return;
if (boolean) {
var timer = setTimeout(function() {
increase(i, true);
console.log(i);
}, 8);
}
}
increase(1,true);
What about you put in some extra argument
to the function?
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.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I have this script:
for (var i = 1; i <= 2; i++) {
setTimeout(function() { alert(i) }, 100);
}
But 3 is alerted both times, instead of 1 then 2.
Is there a way to pass i, without writing the function as a string?
You have to arrange for a distinct copy of "i" to be present for each of the timeout functions.
function doSetTimeout(i) {
setTimeout(function() {
alert(i);
}, 100);
}
for (var i = 1; i <= 2; ++i)
doSetTimeout(i);
If you don't do something like this (and there are other variations on this same idea), then each of the timer handler functions will share the same variable "i". When the loop is finished, what's the value of "i"? It's 3! By using an intermediating function, a copy of the value of the variable is made. Since the timeout handler is created in the context of that copy, it has its own private "i" to use.
Edit:
There have been a couple of comments over time in which some confusion was evident over the fact that setting up a few timeouts causes the handlers to all fire at the same time. It's important to understand that the process of setting up the timer — the calls to setTimeout() — take almost no time at all. That is, telling the system, "Please call this function after 1000 milliseconds" will return almost immediately, as the process of installing the timeout request in the timer queue is very fast.
Thus, if a succession of timeout requests is made, as is the case in the code in the OP and in my answer, and the time delay value is the same for each one, then once that amount of time has elapsed all the timer handlers will be called one after another in rapid succession.
If what you need is for the handlers to be called at intervals, you can either use setInterval(), which is called exactly like setTimeout() but which will fire more than once after repeated delays of the requested amount, or instead you can establish the timeouts and multiply the time value by your iteration counter. That is, to modify my example code:
function doScaledTimeout(i) {
setTimeout(function() {
alert(I);
}, i * 5000);
}
(With a 100 millisecond timeout, the effect won't be very obvious, so I bumped the number up to 5000.) The value of i is multiplied by the base delay value, so calling that 5 times in a loop will result in delays of 5 seconds, 10 seconds, 15 seconds, 20 seconds, and 25 seconds.
Update
Here in 2018, there is a simpler alternative. With the new ability to declare variables in scopes more narrow than functions, the original code would work if so modified:
for (let i = 1; i <= 2; i++) {
setTimeout(function() {
alert(i)
}, 100);
}
The let declaration, unlike var, will itself cause there to be a distinct i for each iteration of the loop.
You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout:
for (var i = 1; i <= 3; i++) {
(function(index) {
setTimeout(function() { alert(index); }, i * 1000);
})(i);
}
This's Because!
The timeout function
callbacks are all running well after the completion of the loop. In fact,
as timers go, even if it was setTimeout(.., 0) on each iteration, all
those function callbacks would still run strictly after the completion
of the loop, that's why 3 was reflected!
all two of those functions, though they are defined
separately in each loop iteration, are closed over the same shared global
scope, which has, in fact, only one i in it.
the Solution's declaring a single scope for each iteration by using a self-function executed(anonymous one or better IIFE) and having a copy of i in it, like this:
for (var i = 1; i <= 2; i++) {
(function(){
var j = i;
setTimeout(function() { console.log(j) }, 100);
})();
}
the cleaner one would be
for (var i = 1; i <= 2; i++) {
(function(i){
setTimeout(function() { console.log(i) }, 100);
})(i);
}
The use of an IIFE(self-executed function) inside each iteration created a new scope for each
iteration, which gave our timeout function callbacks the opportunity
to close over a new scope for each iteration, one which had a variable
with the right per-iteration value in it for us to access.
The function argument to setTimeout is closing over the loop variable. The loop finishes before the first timeout and displays the current value of i, which is 3.
Because JavaScript variables only have function scope, the solution is to pass the loop variable to a function that sets the timeout. You can declare and call such a function like this:
for (var i = 1; i <= 2; i++) {
(function (x) {
setTimeout(function () { alert(x); }, 100);
})(i);
}
You can use the extra arguments to setTimeout to pass parameters to the callback function.
for (var i = 1; i <= 2; i++) {
setTimeout(function(j) { alert(j) }, 100, i);
}
Note: This doesn't work on IE9 and below browsers.
ANSWER?
I'm using it for an animation for adding items to a cart - a cart icon floats to the cart area from the product "add" button, when clicked:
function addCartItem(opts) {
for (var i=0; i<opts.qty; i++) {
setTimeout(function() {
console.log('ADDED ONE!');
}, 1000*i);
}
};
NOTE the duration is in unit times n epocs.
So starting at the the click moment, the animations start epoc (of EACH animation) is the product of each one-second-unit multiplied by the number of items.
epoc: https://en.wikipedia.org/wiki/Epoch_(reference_date)
Hope this helps!
You could use bind method
for (var i = 1, j = 1; i <= 3; i++, j++) {
setTimeout(function() {
alert(this);
}.bind(i), j * 100);
}
Well, another working solution based on Cody's answer but a little more general can be something like this:
function timedAlert(msg, timing){
setTimeout(function(){
alert(msg);
}, timing);
}
function yourFunction(time, counter){
for (var i = 1; i <= counter; i++) {
var msg = i, timing = i * time * 1000; //this is in seconds
timedAlert (msg, timing);
};
}
yourFunction(timeInSeconds, counter); // well here are the values of your choice.
I had the same problem once this is how I solved it.
Suppose I want 12 delays with an interval of 2 secs
function animate(i){
myVar=setTimeout(function(){
alert(i);
if(i==12){
clearTimeout(myVar);
return;
}
animate(i+1)
},2000)
}
var i=1; //i is the start point 1 to 12 that is
animate(i); //1,2,3,4..12 will be alerted with 2 sec delay
the real solution is here, but you need to be familiar with PHP programing language.
you must mix PHP and JAVASCRIPT orders in order to reach to your purpose.
pay attention to this :
<?php
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);
</script>";
}
?>
It exactly does what you want, but be careful about how to make ralation between
PHP variables and JAVASCRIPT ones.