0

I'm trying to fadeout message containers after a few seconds. Usually there's only one, and that works fine, but when there are 2 or more, only the last one seems to get processed.

Code:

function fadeOut(elementToFade) {
    var element = document.getElementById(elementToFade);
    console.log(`fo element = ${element}, ${element.id}`)

    element.style.opacity = (parseFloat(element.style.opacity) - 0.1).toString();
    if (parseFloat(element.style.opacity) <= 0.0) {
        element.style.display = "none";
    } else {
        setTimeout("fadeOut(\"" + elementToFade + "\")", 150);
        // setTimeout("fadeOut(\"" + element.id + "\")", 150);
    }
};

var message_elements_nodelist = document.querySelectorAll("div[id^='message_container']");
var pauseBeforeFadeout = 5000;

for (var i = 0; i < message_elements_nodelist.length; i++) {
    var el = document.getElementById(message_elements_nodelist[i].id)
    console.log(`element = ${el}, ${el.id}`)
    setTimeout(function () {
        el.style.opacity = "1.0";
        fadeOut(el.id);
    }, pauseBeforeFadeout);
};

The console logging shows:

element = [object HTMLDivElement], message_container-0
element = [object HTMLDivElement], message_container-1
2fo element = [object HTMLDivElement], message_container-1
10fo element = [object HTMLDivElement], message_container-1

So the code finds both elements, but then the timeout only seems to get applied to the last one in the list and I cannot work out why this would be.

Edit

Putting in a Promise now looks like this:

const message_elements_nodelist = document.querySelectorAll("div[id^='message_container']")
const pauseBeforeFadeout = 5000

async function fadeOut(elementToFade) {
    const element = document.getElementById(elementToFade);
    console.log(`fo element = ${element}, ${element.id}`)

    element.style.opacity = (parseFloat(element.style.opacity) - 0.1).toString()
    if (parseFloat(element.style.opacity) <= 0.0) {
        element.style.display = "none"
    } else {
        setTimeout("fadeOut(\"" + elementToFade + "\")", 150)
    }
}

async function fadeOutAllMessages() {
    for (let i = 0; i < message_elements_nodelist.length; i++) {
        const el = document.getElementById(message_elements_nodelist[i].id)
        console.log(`element = ${el}, ${el.id}`)
        await new Promise(resolve => setTimeout(() => {
            el.style.opacity = "1.0"
            fadeOut(el.id)    
        }, pauseBeforeFadeout))
    }
}

fadeOutAllMessages()

Unfortunately, now the first message is cleared, but the second isn't, and the console.log messages look like this:

element = [object HTMLDivElement], message_container-0
fo element = [object HTMLDivElement], message_container-0
9 fo element = [object HTMLDivElement], message_container-0

Those last 9 messsages appear 5 seconds after the first, so one await is working, then it seems to bundle them all through with only the 1st loop instance.

michjnich
  • 2,796
  • 3
  • 15
  • 31

1 Answers1

1

This is happening because of asynchronous method (setTimeout in this case) is used inside for loop. Before the asynchronous method execution, variable i is already incremented (the loop does not wait for the asynchronous code inside), hence setTimeout will be executed multiple times and variable i is always at it's last value for all the callbacks.

Consider using async/await inside the loop.

async function yourFunction() {
    for (let i = 0; i < 10; i++) {
        // wait for the promise before incrementing i and advancing the loop
        await new Promise(resolve => resolve(setTimeout(() => console.log(i), 
        1000)))
    }
}

The other solution is calling a function which is created outside of the loop.

const setDelay = (i) => {
    setTimeout(() => {
      console.log(i);
    }, 1000);
  }

for (let i = 0; i < 5; i++) {
    setDelay(i);
}

You can find more examples in this topic: Asynchronous Process inside a javascript for loop

Centmsn
  • 51
  • 6
  • Thanks. This isn't entirely a solution though, sadly, though it's definitely an async issue as you say. I think the recursive call of `fadeOut` makes it more complicated than this. Adding this promise now only removes the first message ... seems like the 5 iterations that should be for message-1 ar enow done on message-0 ... – michjnich Mar 29 '21 at 13:39
  • I just updated my answer to clarify the solution. It looks like You did not wrap `setTimeout` inside `resolve` method. `await new Promise(resolve => resolve(setTimeout(() => {...}, pauseBeforeFadeout)))` should solve the problem. – Centmsn Mar 29 '21 at 15:08
  • Ah, thank you so much! I need to spend some time (when I find some!) to properly understand how the promises stuff works! :) – michjnich Mar 29 '21 at 17:45