setTimeout with each loop does not work

Asked

Viewed 98 times

0

count = 1;
$("a.btns").each(function() {
    if ($(this).parent("span").attr("id") == "btn_default") {
        return;
    }

  count++;
these = $(this);

setTimeout(function() {
$(these).click();
  }, 10000 * count);
});

The problem should be very simple to solve, but I’m cracking my head..

Basically, I try to run about 10 buttons on the page, that meet by the class "btns", and every 10 seconds (as I defined in the timeout), to click the button (one at a time, as is the proposal of the code).

But in practice, only the last button is clicked every time. It seems that in any call of the setTimeout,a inves of the "These" be each button, it picks up the last of the each loop.

What to do in this case?

Thank you

  • 1

    The setTimeout does not maintain the these according to the value it contained at the time it was called. It will always pick up the current reference. At the end of the first ten seconds, the loop each is already over and the reference in these will be the last element. And it will be so for all other times that the setTimeout execute.

1 answer

1


The setTimeout does not keep the variable these according to the value it contained at the time it was called. It will always pick up the current reference. At the end of the first ten seconds, the loop each is already over and the reference in these will be the last element. And it will be so for all other times that the setTimeout execute.

count = 1;
$("a.btns").each(function() {
    if ($(this).parent("span").attr("id") == "btn_default") {
        return;
    }

    count++;
    these = $(this);

    setTimeout(function() {
      $(these).click();
    }, 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
  <a class="btns" onclick="console.log(1)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(2)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(3)"></a>
</span>

This is a common problem that happens when we don’t understand what the closures in Javascript and how it works with the scopes/contexts of functions.

The solution would be to find a way to keep the reference to the button element when calling the function inside the setTimeout. You can play your setTimeout for a parameterized auxiliary function (maintaining these in a different context, unrelated to the context of the function in which it was called):

count = 1;
$("a.btns").each(function() {
    if ($(this).parent("span").attr("id") == "btn_default") {
        return;
    }

    count++;
    these = $(this);
    clickTimeout(these, 1000 * count);
});

function clickTimeout(el, time){
    setTimeout(function() {
        $(el).click();
    }, time);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
  <a class="btns" onclick="console.log(1)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(2)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(3)"></a>
</span>

Or you can use the method bind() Javascript to change the context of this within the function being passed to the setTimeout. We can pass the variable these as the new this function and use it in there without losing your reference:

count = 1;
$("a.btns").each(function() {
    if ($(this).parent("span").attr("id") == "btn_default") {
        return;
    }

    count++;
    these = $(this);

    setTimeout((function(el) {
        this.click();
    }).bind(these), 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
  <a class="btns" onclick="console.log(1)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(2)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(3)"></a>
</span>

I hope I’ve helped.


EDITION

In Javascript ES6 were inserted the variables of type Let and const. They add an interesting feature called Temporal Dead Zones, which allows them to retain the value they contained in the scope when they were executed. It’s a little hard to understand concept.

In practice, his each works as expected with only a small change in the original code:

count = 1;
$("a.btns").each(function() {
    if ($(this).parent("span").attr("id") == "btn_default") {
        return;
    }

    count++;
    const these = $(this); // <--- Agora utiliza "const"

    setTimeout(function() {
      $(these).click();
    }, 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
  <a class="btns" onclick="console.log(1)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(2)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(3)"></a>
</span>

Note that now the variable these is declared using const. This way, your code becomes more intuitive and easy to understand, without having to break it (and thus affect readability) to take into account the and the contexts of this.

Finally, we have yet another way to solve your problem by using Arrow functions, which are also a new Javascript functionality ES6:

count = 1;
$("a.btns").each(function() {
    if ($(this).parent("span").attr("id") == "btn_default") {
        return;
    }

    count++;

    setTimeout(() => $(this).click(), 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
  <a class="btns" onclick="console.log(1)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(2)"></a>
</span>
<span>
  <a class="btns" onclick="console.log(3)"></a>
</span>

As Arrow functions do not have their own context this, and therefore use the this of the lexical context that is just above. This means that when your Arrow Function use the $(this), she will be accessing the object this of the iteration of each in which it was called, also retaining the reference of the button element.

  • Congratulations for the complete answer, and also for presenting alternative solutions. Ball show, a great hug!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.