-2

I would like to wait until a for loop is finished to continue with my function. I can't seem to get it to work. Hello is always logged before the for loop finishes. Why is this, and what would be a way to run only when the for loop has finished. I need to wait for the for loop to finish before continuing within the function, but outside the for loop.

For the people that keep closing this question, this is different. I want to know how to wait until a for loop is finished, and then execute code. Not set timeouts within the for loop. Thanks.

function doMergeAnimations(animations){
    for (let i = 0; i < animations.length; i++) {
        const arrayBars = document.getElementsByClassName('array-bar');
        const isColorChange = i % 3 !== 2;
        if (isColorChange) {
            const [barOneIdx, barTwoIdx] = animations[i];
            const barOneStyle = arrayBars[barOneIdx].style;
            const barTwoStyle = arrayBars[barTwoIdx].style;
            const color = i % 3 === 0 ? INITCOLOR : SWAPCOLOR;
            setTimeout(() => {
            barOneStyle.backgroundColor = color;
            barTwoStyle.backgroundColor = color;
            }, i * animationSpeed);
        } else {
            setTimeout(() => {
            const [barOneIdx, newHeight] = animations[i];
            const barOneStyle = arrayBars[barOneIdx].style;
            barOneStyle.height = `${newHeight}px`;
            }, i * animationSpeed);
        }
    }
    console.log("Hello")
}
Owen Moogk
  • 37
  • 8
  • What do you mean "finish"? A `for` loop will run to completion before other code can engage. There is no way that the loop runs after the logging. That's not how it works. If you want to know more, set a breakpoint at the start of the function or add `debugger` to automatically drop into the debugger there. – tadman Feb 01 '21 at 20:06
  • 2
    I guess you need to wrap your setTimeout in a Promise and then use await/async, but I am not 100% sure what you want. – J. Langer Feb 01 '21 at 20:08
  • "For the people that keep closing this question, this is different." huh? Are you under the impression that there is a specific group of us who read your questions? Questions are generally judged on their merit (or lack thereof), not by the history of the poster, and there are waaaay too many for even the most prolific of us to see more than a microscopic fraction of the total, especially in a popular tag like Javascript. The people who closed your last two questions will likely not ever see this one (note that it was a different person/group of people in each case previously). – Jared Smith Feb 01 '21 at 20:14
  • Does this answer your question? [Sync JS function with for loop](https://stackoverflow.com/questions/65995540/sync-js-function-with-for-loop) – John Montgomery Feb 01 '21 at 20:19

3 Answers3

2

If the actual goal here is to trigger a series of events over time then you can't use a for loop to structure the execution, that doesn't work with asynchronous events. You must restructure it:

function doMergeAnimations(animations){
    let events = [ ];
    for (let i = 0; i < animations.length; i++) {
        const arrayBars = document.getElementsByClassName('array-bar');
        const isColorChange = i % 3 !== 2;
        if (isColorChange) {
            const [barOneIdx, barTwoIdx] = animations[i];
            const barOneStyle = arrayBars[barOneIdx].style;
            const barTwoStyle = arrayBars[barTwoIdx].style;
            const color = i % 3 === 0 ? INITCOLOR : SWAPCOLOR;
            events.push(() => {
            barOneStyle.backgroundColor = color;
            barTwoStyle.backgroundColor = color;
            });
        } else {
            events.push(() => {
            const [barOneIdx, newHeight] = animations[i];
            const barOneStyle = arrayBars[barOneIdx].style;
            barOneStyle.height = `${newHeight}px`;
            });
        }
    }

    let timer = setInterval(() => {
        let fn;

        if (fn = events.shift()) {
          fn();
        }
        else {
          clearInterval(timer);
        }
    }, animationSpeed);

    console.log("Hello");
}

As in this quick demo:

function arrayPopper(array, delay) {
    let events = [ ];

    array.forEach(e => {
      events.push(() => console.log(e));
    })

    let timer = setInterval(() => {
        let fn;

        if (fn = events.shift()) {
          fn();
        }
        else {
          clearInterval(timer);
        }
    }, delay);

    events.push(() => console.log("Hello"));
}

arrayPopper([ 1, 2, 3, 4, 5, 6, 'done' ], 500);

You can even easily Promisify this by adding:

async function doMergeAnimations(animations) {
  return new Promise((resolve, _reject) => {
    let events = [ ];

    // Same code...

    events.push(resolve);
  });
}

Which means you can now do:

// Chained promise
doMergeAnimations(...).then(() => console.log('Done!'));

// async function
await doMergeAnimations(...);
console.log('Done!');
tadman
  • 208,517
  • 23
  • 234
  • 262
1

The problem isn't the loop, the problem is what you're doing inside the loop. You're invoking asynchronous operations with setTimeout, which all happen later. If you want to wait for those to finish then you probably want to wrap setTimeout in a Promise and then await it.

You can wrap it with something like:

const setAwaitableTimeout = async (func, delay) =>
  new Promise((resolve) => setTimeout(() => {
    func();
    resolve();
  }), delay);

Then you can make your function async and use this instead of setTimeout. Which has the added bonus that you don't need to multiply by i in your delays to mimic the awaiting behavior:

async function doMergeAnimations(animations) {
  for (let i = 0; i < animations.length; i++) {
    // existing code, then...
    await setAwaitableTimeout(() => {
      barOneStyle.backgroundColor = color;
      barTwoStyle.backgroundColor = color;
    }, animationSpeed);
    // the rest of your code
  }
  console.log("Hello");
}
David
  • 208,112
  • 36
  • 198
  • 279
1

If you wrap the setTimeouts() in promises you can control the outcome.

Here each animation is placed inside a Promise object and that promise is pushed into an array of Promises. Each setTimeout() then calls resolve() at the completion of the animation.

Once the loop has completed, Promise.all() is used to wait for the completion of each Promise in the array. Finally, inside the .then() of the Promise is when the "loop is finished" (conceptually).

function doMergeAnimations(animations) {
  const animPromises = [];
  for (let i = 0; i < animations.length; i++) {
    const arrayBars = document.getElementsByClassName('array-bar');
    const isColorChange = i % 3 !== 2;
    if (isColorChange) {
      const [barOneIdx, barTwoIdx] = animations[i];
      const barOneStyle = arrayBars[barOneIdx].style;
      const barTwoStyle = arrayBars[barTwoIdx].style;
      const color = i % 3 === 0 ? INITCOLOR : SWAPCOLOR;
      animPromises.push(new Promise(resolve => {
          setTimeout(() => {
            barOneStyle.backgroundColor = color;
            barTwoStyle.backgroundColor = color;
            resolve();
          }, i * animationSpeed)
        });
      }
      else {
        animPromises.push(new Promise(resolve => {
            setTimeout(() => {
              const [barOneIdx, newHeight] = animations[i];
              const barOneStyle = arrayBars[barOneIdx].style;
              barOneStyle.height = `${newHeight}px`;
              resolve();
            }, i * animationSpeed)
          });
        }
      }
      Promise.all(animPromises).then(() => console.log("Hello"));
    }
Randy Casburn
  • 13,840
  • 1
  • 16
  • 31