Wait until setInterval() is done
Asked Answered
K

6

30

I would like to add a small dice-rolling effect to my Javascript code. I think a good way is to use the setInterval() method. My idea was the following code (just for testing):

function roleDice() {
    var i = Math.floor((Math.random() * 25) + 5);
    var j = i;
    var test = setInterval(function() {
        i--;
        document.getElementById("dice").src = "./images/dice/dice" + Math.floor((Math.random() * 6) + 1) + ".png";
        if (i < 1) {
            clearInterval(test);
        }

    }, 50);
}

Now I would like to wait for the setInterval until it is done. So I added a setTimeout.

setTimeout(function(){alert("test")}, (j + 1) * 50);

This code works quite okay. But in my main code the roleDice() function returns a value. Now I don’t know how I could handle that... I can’t return from the setTimeout(). If I add a return to the end of the function, the return will rise too fast. Does anyone have an idea, of how I could fix that?

Edit Hmm, okay I understand what the callback does and I think I know how it works but I have still the problem. I think it’s more of an "interface" problem... Here is my code:

function startAnimation(playername, callback) {
    var i = Math.floor((Math.random() * 25) + 5);
    var int = setInterval(function() {
        i--;
        var number = Math.floor((Math.random() * 6) + 1);
        document.getElementById("dice").src = "./images/dice/dice" + number + ".png";
        if(i < 1) {
            clearInterval(int);
            number = Math.floor((Math.random() * 6) + 1);
            addText(playername + " rolled " + number);
            document.getElementById("dice").src = "./images/dice/dice" + number + ".png";
            callback(number);
        }
    }, 50);
}

function rnd(playername) {
    var callback = function(value){
        return value; // I knew thats pointless...
    };
    startAnimation(playername, callback);
}

The function rnd() should wait and return the value… I’m a little bit confused. At the moment I have no clue how to going on... The code wait for the var callback... but how I could combine it with the return? I would like to run the animation and return after that the last number with rnd() to another function.

Kerwon answered 15/6, 2012 at 17:31 Comment(3)
I think you wanted to call your function rollDice, not roleDice ;)Midweek
also, not sure why you would set both a set interval and set timeout since your interval knows when it is finished, and any code that needs to occur at the end can happen there. There is no guarantee that a set interval and set timeout with the same amount of time will actually end at the same time.Margheritamargi
@Midweek Yea, youre right! :)Kerwon
M
50

You stumbled into the pitfall most people hit at some point when they get in touch with asynchronous programming.

You cannot "wait" for an timeout/interval to finish - trying to do so would not work or block the whole page/browser. Any code that should run after the delay needs to be called from the callback you passed to setInterval when it's "done".

function rollDice(callback) {
    var i = Math.floor((Math.random() * 25) + 5);
    var j = i;
    var test = setInterval(function() {
        i--;
        var value = Math.floor((Math.random() * 6) + 1);
        document.getElementById("dice").src = "./images/dice/dice" + value + ".png";
        if(i < 1) {
            clearInterval(test);
            callback(value);
        }
    }, 50);
}

You then use it like this:

rollDice(function(value) {
    // code that should run when the dice has been rolled
});
Midweek answered 15/6, 2012 at 17:38 Comment(6)
Okay, thanks for your reply, but the code from the callback should return a value. Is that possible? Please see my Edit. GreetzKerwon
What you are trying to do is impossible. When working with callbacks and asynchronous functions, any data you want to return after something has finished needs to be passed to another callback function as an argument.Midweek
Okay, so I have to find a other way or pass the value with the callback. Thanks.Kerwon
Yes, simply take a callback argument. Instead of writing your code like foo = blah(); ... you use blah(function(foo) { ... });Midweek
There are 2 callbacks: setinterval() callback (1st argument) and the rolldice argument callback. You can omit the argument to rolldice and put code directly in the ìf statement. Why complicated..Baerl
I forgot the Call: rollDice() to my post from just now.Baerl
M
34

You can now use Promises and async/await

Like callbacks, you can use Promises to pass a function that is called when the program is done running. If you use reject you can also handle errors with Promises.

function rollDice() {
  return new Promise((resolve, reject) => {
    const dice = document.getElementById('dice');
    let numberOfRollsLeft = Math.floor(Math.random() * 25 + 5);

    const intervalId = setInterval(() => {
      const diceValue = Math.floor(Math.random() * 6 + 1);

      // Display the dice's face for the new value
      dice.src = `./images/dice/dice${diceValue}.png`;

      // If we're done, stop rolling and return the dice's value
      if (--numberOfRollsLeft < 1) {
        clearInterval(intervalId);
        resolve(diceValue);
      }
    }, 50);
  });
}

Then, you can use the .then() method to run a callback when the promise resolves with your diceValue.

rollDice().then((diceValue) => {
  // display the dice's value to the user via the DOM
})

Or, if you're in an async function, you can use the await keyword.

async function takeTurn() {
  // ...
  const diceValue = await rollDice()
  // ...
}
Malvia answered 6/3, 2018 at 20:23 Comment(6)
how can i return a callback function from rollDice to calee when promise rejects instead of returning error messageDiogenes
@Diogenes I'm not sure I entirely understand what you're trying to ask but I think what you want is the .catch function and .reject function. From what I know you should be using the .reject function to return an error message and the catch to handle it accordingly, such as logging.Malvia
@ChrisBrownie55 can you please give an example to test Promise with clearinterval like here.Baerl
@Baerl I'm not sure I understand, clearInterval is a method for removing an interval. If you're looking for something relating to being able to clear the interval if you've got it in this promise format. You'll just need to either pass the interval id down via resolve() or make it global (or the highest scope you need)Malvia
Thanks! Simple but powerful! Or at least if you know what i mean. Thanks anyway!Calistacalisthenics
Thank you so much for this!Gerdy
S
2

Orginally your code was all sequential. Here is a basic dice game where two players roll one and they see who has a bigger number. [If a tie, second person wins!]

function roleDice() {
    return Math.floor(Math.random() * 6) + 1;
}

function game(){    
    var player1 = roleDice(),
        player2 = roleDice(),
        p1Win = player1 > player2;
    alert( "Player " + (p1Win ? "1":"2") + " wins!" );
}

game();

The code above is really simple since it just flows. When you put in a asynchronous method like that rolling the die, you need to break up things into chunks to do processing.

function roleDice(callback) {
    var i = Math.floor((Math.random() * 25) + 5);   
    var j = i;
    var test = setInterval(function(){
        i--;
        var die =  Math.floor((Math.random() * 6) + 1);
        document.getElementById("dice").src = "./images/dice/dice" + die + ".png";
        if(i < 1) {
                clearInterval(test);
                callback(die);  //Return the die value back to a function to process it
            }
        }, 50);
}

function game(){
    var gameInfo = {  //defaults
                       "p1" : null,
                       "p2" : null
                   },
        playerRolls = function (playerNumber) { //Start off the rolling
            var callbackFnc = function(value){ //Create a callback that will 
                playerFinishes(playerNumber, value); 
            };
            roleDice( callbackFnc );
        },
        playerFinishes = function (playerNumber, value) { //called via the callback that role dice fires
            gameInfo["p" + playerNumber] = value;
            if (gameInfo.p1 !== null && gameInfo.p2 !== null ) { //checks to see if both rolls were completed, if so finish game
                giveResult();
            }
        },
        giveResult = function(){ //called when both rolls are done
            var p1Win = gameInfo.p1 > gameInfo.p2;
            alert( "Player " + (p1Win ? "1":"2") + " wins!" );
        };            
    playerRolls("1");  //start player 1
    playerRolls("2");  //start player 2
}

game();

The above code could be better in more of an OOP type of way, but it works.

Smaze answered 15/6, 2012 at 18:34 Comment(0)
S
1

There are a few issues for the above solutions to work. Running the program doesn't (at least not in my preferred browser) show any images, so these has to be loaded before running the game.

Also, by experience I find the best way to initiate the callback method in cases like preloading N images or having N players throw a dice is to let each timeout function do a countdown to zero and at that point execute the callback. This works like a charm and does not rely on how many items needing to be processed.

<html><head><script>
var game = function(images){
   var nbPlayers = 2, winnerValue = -1, winnerPlayer = -1;
   var rollDice = function(player,callbackFinish){
      var playerDice = document.getElementById("dice"+player);
      var facesToShow = Math.floor((Math.random() * 25) + 5);   
      var intervalID = setInterval(function(){
         var face =  Math.floor(Math.random() * 6);
         playerDice.src = images[face].src;
         if (--facesToShow<=0) {
            clearInterval(intervalID);
            if (face>winnerValue){winnerValue=face;winnerPlayer=player}
            if (--nbPlayers<=0) finish();
         }
      }, 50);
   }
   var finish = function(){
      alert("Player "+winnerPlayer+" wins!");
   }      
   setTimeout(function(){rollDice(0)},10);
   setTimeout(function(){rollDice(1)},10);
}
var preloadImages = function(images,callback){
   var preloads = [], imagesToLoad = images.length;
   for (var i=0;i<images.length;++i){
      var img=new Image();
      preloads.push(img);
      img.onload=function(){if(--imagesToLoad<=0)callback(preloads)}
      img.src = images[i];
   }
}
preloadImages(["dice1.png","dice2.png","dice3.png","dice4.png","dice5.png","dice6.png"],game);
</script></head><body>
<img src="" id="dice0" /><img src="" id="dice1" /></body></html>
Syndactyl answered 10/7, 2012 at 0:3 Comment(0)
X
1

To achieve that goal, using vanilla setInterval function is simply impossible. However, there is better alternative to it: setIntervalAsync.

setIntervalAsync offers the same functionality as setInterval, but it guarantees that the function will never executed more than once in a given interval.

npm i set-interval-async

Example:

setIntervalAsync(
  () => {
      console.log('Hello')
      return doSomeWork().then(
        () => console.log('Bye')
      )
  },
  1000
)
Xebec answered 6/7, 2021 at 16:48 Comment(0)
M
1

Example with Promises & setIntervals.. this is how I created a 'flat' chain of functions that wait until the other is completed...

The below snippet uses a library called RobotJS (here it returns a color at a specific pixel on screen) to wait for a button to change color with setInterval, after which it resolves and allows the code in the main loop to continue running.

So we have a mainChain async function, in which we run functions that we declare below. This makes it clean to scale, by just putting all your await someFunctions(); one after each other:

async function mainChain() {
  await waitForColorChange('91baf1', 1400, 923)

  console.log('this is run after the above finishes')
}




async function waitForColorChange(colorBeforeChange, pixelColorX, pixelColorY) {
  return new Promise((resolve, reject) => {
    let myInterval = setInterval(() => {
      let colorOfNextBtn = robot.getPixelColor(pixelColorX, pixelColorY)

      if (colorOfNextBtn == colorBeforeChange) {
        console.log('waiting for color change')
      } else {
        console.log('color has changed')
        clearInterval(myInterval);
        resolve();
      }
    }, 1000)
  })
}


//Start the main function
mainChain()
Medullated answered 22/8, 2022 at 18:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.