Search code examples
javascriptloopsdelay

How do I loop trough a list of strings and decrease the looping speed at the same time in js?


I want to create a javascript that loops trough all names and always displays the current name of the loop in the UI (html). But at the same time the loop-speed should decrease.

So lets say we start at a time of 10 loops / second. It should then decrease until its 1 loop / second and then stop at that point and display the name. Its kind of a raffle and the winner should be random.

So I created the script below but the problem is that nothing happens? Somehow just nothing happens. It executes the code but no errors and nothing changes in the UI. The code does get executed (tested it by logging to console)!

My function:

function Roll(){
    var delay = 100;
    for(var i = 0; delay < 1000; i++){
        delay += 25;
        if(i >= listOfParticipants.length){
            i = 0;
        }
        setTimeout(function() {
            document.getElementById("person_name").value = listOfParticipants[i];
        }, delay);
    }
}

the html element:

<h1 class="text-center mt-2" id="person_name" ></h1>

EDIT: I think I know what the problem is, but I don't know how to solve it. I think the for loop doesnt wait for the setTimeout to finish. Don't know how to handle it.

EDIT 2: It seems that setTimeout creates a new ExecutionContext. That means the for-loop ends and gets its max value (13) before the setTimeout gets executed. The question now is how do I add a timeout in the current ExecutionContext? The list has only 12 objects thats why the result with i = 13 is undefined.


Solution

  • Assuming that you want to stop on the last name of the array (since it wasn't clear in your question how name selection should work), there is a strategy that you can use.

    Basically, instead of looping through your duration, you want to loop through your array of listOfParticipants. At each iteration, we decrease the duration that we wait before continuing on to the next iteration of the for loop.

    // Pseudo code!
    async function roll() {
      for (let i = 0; i < listOfParticipants.length; i++) {
        // Wait for promise to resolve
        await someKindOfPromise();
    
        // Update name
        document.getElementById('person_name').innerText = listOfParticipants[i];
      }
    }
    

    This can be done using async/await. The native for loop in ES6 supports awaiting for a promise, so we simply create a method that returns a promise that resolves after x number of seconds:

    // A simple promise that resolves after a specified duration
    function wait(duration) {
      return new Promise(resolve => {
        window.setTimeout(resolve, duration);
      });
    }
    

    After x number of second passes, we move on to the next iteration.

    The value of x can be simply be determined by decrementing a pre-set duration based on our relative position along the participants array. The further you are in the array (indicated by the index i), the shorter you want the time to be, i.e.: delay * i / listOfParticipants.length;

    async function roll() {
      const delay = 100;
      for (let i = 0; i < listOfParticipants.length; i++) {
        // Force the for loop to wait for a set amount of time
        const duration = delay * i / listOfParticipants.length;
        await wait(duration);
    
        // Once the duration has been awaited, we can then update the inner text
        document.getElementById('person_name').innerText = listOfParticipants[i];
      }
    }
    

    See proof-of-concept below:

    const listOfParticipants = ['Adena', 'Socorro', 'Germaine', 'Ebony', 'Raul', 'Anton', 'Rochel', 'Morgan', 'Joanie', 'Ellsworth', 'Edelmira', 'Susannah', 'Gino', 'Vicenta', 'Katrina', 'Devorah', 'Olinda', 'Lise', 'Napoleon', 'Dessie', 'Herta', 'Cassaundra', 'Nadine', 'Dalton', 'Mica', 'Haydee', 'Linh', 'Williemae', 'Desiree', 'Philomena', 'Julio', 'Darell', 'Shana', 'Ligia', 'Melita', 'Laurene', 'Darby', 'Gregg', 'Shemika', 'Tesha', 'Benita', 'Hyman', 'Kattie', 'Mary', 'Julienne', 'Claud', 'Heather', 'Toney', 'Vasiliki', 'Stephani', 'Violette', 'Barney', 'Warren', 'Felix', 'Mathew', 'Blair', 'Jamar', 'Grover', 'Bud', 'Barbie', 'Gina'];
    
    // A simple promise that resolves after a specified duration
    function wait(duration) {
      return new Promise(resolve => {
        window.setTimeout(resolve, duration);
      });
    }
    
    async function roll() {
      const delay = 100;
      for (let i = 0; i < listOfParticipants.length; i++) {
        // Force the for loop to wait for a set amount of time
        const duration = delay * i / listOfParticipants.length;
        await wait(duration);
        
        // Once the duration has been awaited, we can then update the inner text
        document.getElementById('person_name').innerText = listOfParticipants[i];
      }
    }
    
    document.querySelector('#btn').addEventListener('click', () => roll());
    <h1 class="text-center mt-2" id="person_name"></h1>
    <button id="btn">Raffle</button>