cancel setTimeout inside an IIFE inside a loop - javascript

I have a function that will click on dom elements in order to show and hide them.
The function is working correctly and cycling through my dom elements however I am not able to stop the setTimeout once I call the function due to the IIFE I am using.
I've added a variable to the global space to break out of the loop however the setTimeout is already initiated so it never stops. I have also tried to assigne a name to the first setTimeout with a truthy/false flag in an attempt to catch and stop the next iteration of setTimeout in the loop but that has been unsuccessful as the console log continues when I attempt to stop the setTimeout.
function cycleSlides() {
var slides = [1,2,3,4]
var closeSlide = $(".close");
for(var i = 0; i < slides.length; i++) {
(function() {
var x = i;
setTimeout(function() {
if(x == 0) {
console.log("slides[x].click()")// first slide
}
else {
setTimeout(function() { console.log("closeSlide.click()") }, 1000) // timeout for animation to finish when clicked
setTimeout(function() { console.log("slides[x].click()") }, 2000) // timeout for animation to finish when clicked
}
// CLOSE LAST SLIDE
if(x == slides.length - 1 ) {
setTimeout(function() {
console.log("closeSlide.click()")
}, x * 1000)
}
}, x * 3000)
}(i))
}
}

setTimeout() returns a timeout ID that can passed to clearTimeout() to cancel the timeout. You could store the timeout IDs returned by your calls to setTimout() in an array. Then, when you want to cancel them, call clearTimeout() with the values and clear the array. Here's what that code might look like:
var timeouts = [];
function cancelTimeouts() {
for (var i = 0; i < timeouts.length; i++) {
clearTimeout(timeouts[i]);
}
timeouts = []
}
function cycleSlides() {
var slides = [1,2,3,4]
var closeSlide = $(".close");
for(var i = 0; i < slides.length; i++) {
(function() {
var x = i;
timeouts.push(setTimeout(function() {
if(x == 0) {
console.log("slides[x].click()")// first slide
}
else {
timeouts.push(setTimeout(function() { console.log("closeSlide.click()") }, 1000)) // timeout for animation to finish when clicked
timeouts.push(setTimeout(function() { console.log("slides[x].click()") }, 2000)) // timeout for animation to finish when clicked
}
// CLOSE LAST SLIDE
if(x == slides.length - 1 ) {
timeouts.push(setTimeout(function() {
console.log("closeSlide.click()")
}, x * 1000))
}
}, x * 3000))
}(i))
}
}

Related

How to update a variable to use in another scope

I have a variable let second = 20 that i make 1 lower until it hits 0. When it hits 0 i want to stop running a part of my code but the variable second is always 20 when i use it because i make it lower in another scope. Sorry if my explenation is a bit unclear.
Here is the code:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
}, 1000);
});
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
You could put the if (second > 0) inside the click function that way it will check for the most recent value of second instead of just once on load like so
for(let i = 0; i < imposters.length; i++){
imposters[i].addEventListener("click", function () {
if (second > 0) {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
}
});
The problem has nothing to do with scope. It has to do with timing. That last part of your code only runs once, before the interval runs twenty times.
Here's the order of operations:
Initialize second to 20.
Bind the countdown function to window.onload. (This does not run yet)
Check if seconds is greater than 0, and it is because the intervals haven't run yet. This is the only time this code ever runs.
window.onload is triggered, and your countdown begins
one second later, seconds is now 19
19 seconds later seconds is not 0, and the interval is cleared.
So what you need to do is trigger your code in each iteration of the interval.
You want something closer to:
let second = 20;
window.addEventListener("load", function () {
const myinterval = setInterval(function () {
second--;
// other logic here...
if (second > 0) {
countdownTick(); // runs every second with the interval handler
}
if (second == 0) {
clearInterval(myinterval);
// cleanup
}
}, 1000);
});
function countdownTick() {
// Do the stuff you need to do each second here
}
Your setInterval runs every second. That does not mean that the rest of the code also runs every second. Which is why second is still 20 when the code gets to if (second > 0).
So you need to make sure that this part of your code runs every second as well. One solution would be to wrap that code inside a function which you call inside the interval, like this:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
check();
}, 1000);
});
function check() {
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
}

clearTimeout(timer) not taken into account - New timer variable created each time

In the current slideshow I'm building, I use a timer variable to rotate the slides automatically every 4s. Since manual controls are also present, I wanted to reset this timer whenever the controls are used to avoid any premature succession between two slides, but didn't manage to do it.
I supposed it to be a scope problem, but the timer variable is out of the functions that are trying to share it (showSlide(n) and changeSlide(n)). Yet a brand new timer variable seems to be created each time the changeSlide function is called : the slides automatic rotation quickens each time the "next" control is used, as if multiple timeouts were calling the function simultaneously. What is wrong here ?
const slideshows = document.getElementsByClassName("js-slideshow");
[].forEach.call(slideshows, function(slideshow) {
slideshowlize(slideshow);
});
function slideshowlize(slideshow){
const desc = slideshow.getElementsByClassName("js-desc");
const slide = slideshow.getElementsByClassName("js-slide");
let timer;
let index = 0;
const slidePrev = slideshow.querySelector('.js-prev');
const slideNext = slideshow.querySelector('.js-next');
function showSlide(n){
clearTimeout(timer); // This one is not used yet
if(n < 0){
n = slide.length -1;
}
else if(n > slide.length -1){
n = 0;
}
let i;
for(i = 0; i < slide.length; i++){
slide[i].classList.remove("is-shown");
}
for(i = 0; i < desc.length; i++){
desc[i].classList.remove("is-shown");
}
slide[n].classList.add("is-shown");
desc[n].classList.add("is-shown");
index = n;
timer = setTimeout(function(){
changeSlide(1);
}, 4000);
}
function changeSlide(n){ // this is where the magic doesn't happen
clearTimeout(timer);
if (n > 0){
showSlide(index += 1);
} else {
showSlide(index -= 1);
}
timer = setTimeout(function(){
changeSlide(1);
}, 4000);
}
showSlide(index);
slidePrev.addEventListener('click', function(){
changeSlide(-1);
});
slideNext.addEventListener('click', function(){
changeSlide(1);
});
}
Edit : Two different timers were set up. Since showSlide(n) were already resetting the timer, changeSlide(n) had no need to do it too. Thanks to Bergi for pointing it out.
function changeSlide(n){
//removed "clearTimeout(timer);"
if (n > 0){
showSlide(index += 1);
} else {
showSlide(index -= 1);
}
//removed "timer = setTimeout(...);"
}

Adding 2 Delays In Javascript Loop

I am building a javascript function that displays a popup 10 times each time for 5 seconds.
Inside my code I have something like this:
for (i=0; step < 10; i++)
{
showPopup();
//need to add 5 second delay here
hidePopup();
//need to add a 5 second delay again
}
I have tried settimeout funcion but am not able to syncronize the delays.
I would appreciate anyone helping me to complete this.
You can utilize await inside your loop to wait for a Promise that resolve after 5 seconds to create delays.
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function main() {
for (let i = 0; i < 10; i++) {
showPopup();
await delay(5000);
hidePopup();
await delay(5000);
}
}
You can use setTimeout and to synchronise the delays use the iteration's index, here is an example:
for (i=0; i < 10; i++) {
setTimeout(showPopup, (i * 2) * 500);
setTimeout(hidePopup, ((i * 2) + 1) * 500);
}
function showPopup() {
console.log("#popup show");
}
function hidePopup() {
console.log("#popup hide");
}
In this example, I've set the delay to 500 milliseconds instead of 5 seconds so you won't have to wait too long to see the effect.
You could do with setInterval for repeat the loop ever 5 second and use settimeout for display time of the data
var interval = '';
var count = 1; //count for check 10 times
function show() {
if (count <= 10) {
interval = setInterval(() => {
count++;
el.style.display = 'block'
clearInterval(interval)
setTimeout(hide, 5000)
}, 5000);
}
}
var el = document.querySelector('p');
function hide() {
el.style.display = 'none';
show()
}
show()
p {
display: none
}
<p>hello</p>
Use SetInterval instead of loop and then stop the setInterval using clearInterval

Set timer for loop in javascript

I want to set timer-based for loop in JavaScript.
for (var i = 0; i < 20; i++) {
console.log(i)
}
How I can I repeat this loop every second and show the value of i (the counter)?
if you want to control your loops wait time you can combine settimeout with recursion
var i = 0;
function callMe() {
var timetowait = 100;
// some condition and more login
i++;
if(i < 20) {
setTimeout(callMe, timetowait);
}
}
callMe();
I think this is what you are looking for:
var counter = 0;
setInterval( function(){
console.log(counter);
counter++;
},1000);
You can try this approach too:
function loop(start, end, delay, fn) {
if (start > end) return;
function step(){
// callback fn with current iteration and
// recursively calls loop
fn(start);
loop(start + 1, end, delay, fn);
}
setTimeout(step, delay);
}
usage :
loop(1, 20, 1000, console.log)
var i = 0;
function myFunc() {
console.log(i);
i++;
if(i == 20) {
clearInterval(interval);
}
}
var interval = setInterval(myFunc, 1000);
The setInterval() method calls a function or evaluates an expression at -
specified intervals (in milliseconds).
The setInterval() method will continue calling the function until clearInterval() is called, or the window is closed.

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()

Categories