'Wait until setInterval() is done
I would like to add a small dice rolling effect to my Javascript code. I think a good ways is to use the setInterval() method. My idea was 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 raised to fast. Does anyone have a idea, how I could fix that?
Edit Hmm, okay I understand what the callback dose and I think I know how it works but I have still the problem. I think it’s more a "interface" problem... Here 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 combined it with the return? I would like to run the animation and return after that the last number with rnd() to a other function.
Solution 1:[1]
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()
// ...
}
Solution 2:[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.
Solution 3:[3]
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>
Solution 4:[4]
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
)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | epascarello |
| Solution 3 | Christian Hammer |
| Solution 4 | smac89 |
