等待 setInterval() 完成

30

我想在我的Javascript代码中添加一个小的掷骰子效果。我认为使用setInterval()方法是一个好方法。我的想法是下面这段代码(仅用于测试):

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);
}

现在我想等待setInterval执行完成后再进行操作,所以我添加了一个setTimeout。

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

这段代码运行得非常好。 但在我的主代码中,roleDice() 函数返回一个值。现在我不知道该如何处理它... 我不能从 setTimeout() 中返回。如果我在函数末尾添加一个 return,那么返回值将会过早地返回。有没有人有想法,知道我该如何解决这个问题?

编辑 嗯,好的,我理解回调函数的作用并且我认为我知道它是如何工作的,但我还是有问题。我认为这更像是一个“接口”问题... 这是我的代码:

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);
}

函数rnd()应该等待并返回值...我有点困惑。目前我不知道该怎么继续下去......代码等待var callback...,但是我该如何将其与返回结合起来呢?我想运行动画,然后使用rnd()将最后一个数返回给另一个函数。


8
我认为你想要将你的函数命名为 rollDice 而不是 roleDice ;) - ThiefMaster
此外,我不确定为什么您会同时设置setInterval和setTimeout,因为您的interval知道何时完成,并且任何需要在最后发生的代码都可以在那里发生。不能保证具有相同时间量的setInterval和setTimeout实际上会在同一时间结束。 - dqhendricks
@ThiefMaster 是的,你说得对! :) - Andre Hofmeister
6个回答

50

当你接触异步编程时,你陷入了大多数人在某个时候都会遇到的陷阱。

你不能"等待"超时/间隔完成 - 尝试这样做将不起作用或者阻塞整个页面/浏览器。任何应该在延迟之后运行的代码需要从你传递给setInterval的回调函数中调用。

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);
}

然后您可以像这样使用它:

rollDice(function(value) {
    // code that should run when the dice has been rolled
});

1
好的,谢谢您的回复,但是回调函数中的代码应该返回一个值。这可能吗?请看我的编辑。问候 - Andre Hofmeister
你想要做的是不可能的。当使用回调和异步函数时,任何在某些操作完成之后要返回的数据都需要作为参数传递给另一个回调函数。 - ThiefMaster
好的,那我得找另一种方法或者通过回调函数传递值。谢谢。 - Andre Hofmeister
是的,只需使用回调参数即可。你可以将代码从 foo = blah(); ... 改为 blah(function(foo) { ... }); - ThiefMaster
有两个回调函数:setinterval() 回调函数(第一个参数)和 rolldice 参数回调函数。您可以省略 rolldice 参数并直接将代码放在 if 语句中。为什么要复杂化呢? - Timo
我刚才发布的帖子中忘记了调用 rollDice() 函数。 - Timo

33

现在您可以使用 Promises 和 async/await

与回调函数类似,您可以使用 Promises 来传递一个在程序运行完成时被调用的函数。如果您使用 reject ,您也可以使用 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()方法,在承诺用您的diceValue解决时运行回调函数。

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

或者,如果你在一个 async 函数中,你可以使用 await 关键字。

async function takeTurn() {
  // ...
  const diceValue = await rollDice()
  // ...
}

当 Promise 拒绝时,如何从 rollDice 返回回调函数给调用者而不是返回错误消息? - kumar
1
@kumar,我不确定我完全理解你想问什么,但我认为你想要的是.catch函数和.reject函数。据我所知,你应该使用.reject函数返回错误消息,并使用catch来处理它,例如记录日志。 - Andria
@ChrisBrownie55,你能否提供一个测试Promiseclearinterval的例子,就像这里一样。 - Timo
@Timo 我不确定我理解了,clearInterval 是一种用于删除间隔的方法。如果您正在寻找与能够在此承诺格式中清除间隔有关的内容。您只需要通过 resolve() 将间隔 ID 向下传递或将其设置为全局变量(或您需要的最高范围)。 - Andria
谢谢!简单但功能强大!或者至少如果你知道我的意思的话。不管怎样还是谢谢! - Jarrett
非常感谢您! - Kamal Alhomsi

2

最初你的代码都是按顺序执行的。这里有一个基本的骰子游戏,两个玩家轮流掷骰子,看谁得到更大的数字。[如果平局,则第二个人获胜!]

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();

以上代码非常简单,因为它只是流程。当您使用像掷骰子这样的异步方法时,您需要将事情分成块来进行处理。

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();

上述代码可以更好地采用面向对象的方式编写,但它能正常工作。


1
为了实现这个目标,仅使用原始的 setInterval 函数是不可能的。然而,有一个更好的替代方案:setIntervalAsync。
setIntervalAsync 提供与 setInterval 相同的功能,但它保证在给定的时间间隔内该函数不会执行超过一次。
npm i set-interval-async

例子:

setIntervalAsync(
  () => {
      console.log('Hello')
      return doSomeWork().then(
        () => console.log('Bye')
      )
  },
  1000
)

1

使用Promise和setInterval的示例。这是我创建的一个“平面”函数链,每个函数都等待其他函数完成...

下面的代码片段使用一个名为RobotJS的库(它返回屏幕上特定像素的颜色)来使用setInterval等待按钮改变颜色,然后解析并允许主循环中的代码继续运行。

因此,我们有一个名为mainChain的异步函数,在其中运行我们在下面声明的函数。这使得它易于扩展,只需将所有的await someFunctions();放在彼此之后:

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()

1

以上解决方案存在一些问题。在运行程序时,没有任何图像显示(至少在我首选的浏览器中),因此必须在运行游戏之前加载这些图像。

此外,通过经验,我发现在预加载N个图像或让N个玩家掷骰子等情况下启动回调方法的最佳方法是让每个超时函数倒计时到零,然后执行回调。这非常有效,并且不依赖于需要处理的项目数量。

<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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接