user164863
user164863

Reputation: 641

JS - How to repetativly fire a function at random time for certain period

I would like to imitate progress bar getting random lengh added 0% to 100% with series of messages displayed in certain sequence but also in random time.

In order to do that I'm staring two independent loops using JS setTimeout method with a random number genearted inside of each loop. But each loop not supposed to be longer than 30 seconds and with the way I'm doing I can't control that.

For the second loop I have to fire the function for 11 times randomly but fit that into 30 seconds as well.

The progress bar moves by changing its width. The messages supposed to be right above that. It looks like this:

HTML:

<span id="progressMsg"></span>
<input type="hidden" id="progressMsgNumber">
<div class="pogress progress-info slim" id="progressBar" style="width=1%"></div>

JS:

setTimeout(progressBar, 1000);
setTimeout(progressMsn, 1000);

function progressBar() {
    barWidth = document.getElementById("progressBar").style.width.slice(0, -1); //to get number without "%" char
    if (barWidth >=100) {
        // We are done
    } else {
        minRand = 1;
        maxRand = 5;
        randSeconds = Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand;
        barWidth = Number(barWidth)+randSeconds;
        document.getElementById("progressBar").style.width = barWidth+"%";
        setTimeout(progressBar, randSeconds*1000);
    }

}

function progressMsg() {
    msgList = new array ("Msg1", "Msg2", ... "Msg11");
    barWidth = document.getElementById("progressBar").style.width.slice(0, -1);
    if (barWidth >=100) {
        // We are done
    } else {
        msgNumber = document.getElementById("progressMsgNumber").val;
        msgNumber++;
        document.getElementById("progressMsg").innerHTML = msgList[msgNumber];
        document.getElementById("progressMsgNumber").val = msgNumber;
        minRand = 1;
        maxRand = 5;
        randSeconds = Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand;
        setTimeout(progressMsg, randSeconds*1000);
    }

}

Upvotes: 0

Views: 126

Answers (3)

Jaromanda X
Jaromanda X

Reputation: 1

The key is to create ALL the random times beforehand, then you can manipulate the times in such a way as to guarantee exact 30 seconds total time.

Note: code below, for demo purposes, has times 1/10th of what you would use, i.e. you'd want

        minRand = 1000,
        maxRand = 5000,
        maxTime = 30000;

function progressMsg() {       
    const msgList = ["Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9", "Msg10", "Msg11"],
        minRand = 100,
        maxRand = 500,
        maxTime = 3000;
    let triggers = Array.from({length:11}, () => Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
    triggers.forEach((n, i, a) => a[i] = (a[i-1]||0) + a[i]);
    const mult = maxTime / triggers.slice(-1);
    triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));
    
    const update = (t, i) => {
        console.log(t, Math.round(t/maxTime * 100) + '%', msgList[i]);
    }
    triggers.forEach((n, i) => setTimeout(update, n, n, i));
}
progressMsg();

to make it work with your code

function progressMsg() {       
    const msgList = ["Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9", "Msg10", "Msg11"],
        minRand = 1000,
        maxRand = 5000,
        maxTime = 30000;
    // create an array of random times, e.g. 1,3,4,2,1...
    let triggers = Array.from({length:11}, () => Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
    // accumulate times, so we'd have e.g. 1,4,8,10,11...
    triggers.forEach((n, i, a) => a[i] = (a[i-1]||0) + a[i]);
    const mult = maxTime / triggers.slice(-1);
    // adjust so the last time is exactly maxTime
    triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));

    const update = i => {
        document.getElementById("progressMsg").innerHTML = msgList[i];
        document.getElementById("progressMsgNumber").val = i;
    }
    // start all the timeouts at once, because we have cumulative times
    triggers.forEach((n, i) => setTimeout(update, n, i));
}

function progressBar() {
    const minRand = 1000,
        maxRand = 5000,
        maxTime = 30000,
        bar = document.getElementById("progressBar");
    let time = 0;
    let triggers = [];
    // here we just keep adding random times until we reach maxTime
    while (time < maxTime) {
        triggers.push(time += Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
    }
    const mult = maxTime / triggers.slice(-1);
    // adjust times so last time is exactly maxTime
    triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));

    const update = t => {
        bar.style.width = (100*t/maxTime) + '%';
    }
    triggers.forEach(n => setTimeout(update, n, n));
}

In answer to comment - one way to know when all is done is using promises

const delay = (fn, delay, ...params) => new Promise(resolve => setTimeout(fn, delay, ...params));

function progressMsg() {       
    const msgList = ["Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9", "Msg10", "Msg11"],
        minRand = 1000,
        maxRand = 5000,
        maxTime = 30000;
    // create an array of random times, e.g. 1,3,4,2,1...
    let triggers = Array.from({length:11}, () => Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
    // accumulate times, so we'd have e.g. 1,4,8,10,11...
    triggers.forEach((n, i, a) => a[i] = (a[i-1]||0) + a[i]);
    const mult = maxTime / triggers.slice(-1);
    // adjust so the last time is exactly maxTime
    triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));

    const update = i => {
        document.getElementById("progressMsg").innerHTML = msgList[i];
        document.getElementById("progressMsgNumber").val = i;
    }
    // start all the timeouts at once, because we have cumulative times
    return Promise.all(triggers.map((n, i) => delay(update, n, i)));
}

function progressBar() {
    const minRand = 1000,
        maxRand = 5000,
        maxTime = 30000,
        bar = document.getElementById("progressBar");
    let time = 0;
    let triggers = [];
    // here we just keep adding random times until we reach maxTime
    while (time < maxTime) {
        triggers.push(time += Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
    }
    const mult = maxTime / triggers.slice(-1);
    // adjust times so last time is exactly maxTime
    triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));

    const update = t => {
        bar.style.width = (100*t/maxTime) + '%';
    }
    return Promise.all(triggers.map(n => delay(update, n, n)));
}

// usage

Promise.all([progressMsg(), progressBar()]).then(() => {
    // all done here
});

Upvotes: 1

k0hamed
k0hamed

Reputation: 502

you can store the full spent time and number of displayed messages in variables

let spentTime = 0; // Declare 
let displayedMessages = 0; 
function progressMsg() {
    ....
        setTimeout(() => { // Edit 
          progressMsg();
          spentTime += randSecods; // add the seconds of this tick
          displayedMessages++;
        }, randSeconds*1000);
    }

}

you can then use it to change the min and max in any tick passed on the spent time and the left messages something like

maxSeconds = (fullTime - spentTime) / (numOfMessages - displayedMessages)

will make sure all the messages are displayed, you can change the min value too, to seem more naturally.

Upvotes: 1

kgunbin
kgunbin

Reputation: 61

You can wrap your functions into Promise, with one additional Promise to resolve itself in 30 seconds, and then use Promise.race See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race

Upvotes: -1

Related Questions