JavaScript help - looping with delay

Hi guys and gals,

I wonder if you would be able to help me. I have a function that removes all items in a cart, the code (while loop) works, but I want to remove one at a time, with a visible delay (say 200ms).

It looks like an IIFE would be the way to go with a setTimeout function, but I can’t get it to work. Any ideas? – N.B. My failed attempts are commented out.

Any help is greatly appreciated. Thanks :slight_smile:

removeAll() {
    // This works!
    while (this.cart.childElementCount > 2) {
      this.cart.removeChild(this.cart.children[0]);
    }
    /* *** : Failed Attempts - These don't work : ***
    // Attempt #1
    while (this.cart.childElementCount > 2) {
      (() => { 
        setTimeout(() => {
          this.cart.removeChild(this.cart.children[0]);
        },200);
      })();
    }
    ***************************
    // Attempt #2
    (function removeFirst() {
      if (this.cart.childElementCount > 2) {
        setTimeout(() => { this.cart.removeChild(this.cart.children[0]); }, 200);
        removeFirst();
      }
    })(); *** */
  }

Is there any particular technical reason you want it to be delayed, besides the visual aspect?

I say so since JS is by nature a synchronous language and manually force async generally involves some tweaking.

The easiest approach would be increasing the timeout based on a loop condition (usually index)

for(let i = 0; i < 3; i++) {
 setTimeout(myFunc, i *10)
}

// the program generates a stack of 3 function delayed by 0, 10, 20 ms

A more robust approach would probably involve wrapping the delete function inside a Promise and then call each only after the previous Promise resolve.
This will easily lead to the so called callback-hell.

However if the reasoning behind this is purely visual, you don’t really need to turn a sync function into an async one, you can simply use a delayed css animation to give the “impression” of a one by one action.

Hope this helps :+1:

I actually just had this issue a hour back…

Basically, what I found was that this is cleared from memory before the timeout function runs.

Not sure if it would work in you situation with the loop (over even if it’s good practice), but:

    while (this.cart.childElementCount > 2) {
      (() => { 

let tempCart = this.cart
 
        setTimeout(() => {
          tempCart.removeChild(tempCart.children[0]);
        },200);
      })();
    }

Be aware guys, that if you go for that approach you are not really creating a one by one effect, but rather a delayed effect.

Since all the setTimeout have the same delay, what will happen is that all those function will resolve at once after the given time.

You can see it by increasing the delay time to some seconds and logging some string in the console (or using other console method).

1 Like

Ah yes. You are correct, didn’t think about that. The loop will run quickly and start all the “Timeouts” at the same time (basically)

My issue didn’t have a loop, just a click event with ‘cooldown’ and “this” was removed from memory before callback could run.

You are right that the delay would need to increase with each iteration to get them to remove uniformly.

@sgedye let us know how it turns out and post a CodePen if possible, Happy Coding!

Thanks for you comments, and yes, it was just for the visual aspect. However, ideally I would have wanted the sub-total to update. I couldn’t get the delayed css animation to work, so I did the delay using javascript. I probably could have done it with promises, but that would have added complexity.

I ended up at an acceptable solution using two loops and timeouts (one to add a css class and the other to remove the element). You can see the project here (note, this project was originally forked from john-smilga).

Here is the function:

removeAll() {
    let numItems = this.cart.childElementCount-2; /* Minus two because the buttons are elements too */
    for (let i = 0; i<numItems; i++) {
      setTimeout(() => { this.cart.children[i].classList.add('empty-cart'); }, i*50)
    }
    setTimeout(() => {
      while (this.cart.childElementCount > 2) {
        this.cart.removeChild(this.cart.children[0]);
      }
      showTotals();
      this.showCart();
    }, numItems * 50);
  }

and CSS:

.empty-cart {
  display: none !important;
}