【问题标题】:Pause and restart setTimeout in loop在循环中暂停并重新启动 setTimeout
【发布时间】:2020-09-24 01:25:44
【问题描述】:

我通过等待返回PromisesetTimeout() 的函数来延迟我的循环。我能够启动和重置循环,但也希望能够以某种方式暂停循环。再次单击开始按钮后,我想从数组中的最后一个循环元素继续循环。

$(document).ready(() => {
    $('#btn-start').click(() => {
        loopMoves();
    });
    $('#btn-pause').click(() => {
        // ...
    });
    $('#btn-reset').click(() => {
        clearTimeout(timer);
    });
})

var moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];

async function loopMoves() {
    for(let i = 0; i < moves.length; i++){
        console.log(moves[i]);
        await delay(2000);
    }
}

var timer;
function delay(ms) {
    return new Promise((x) => {
        timer = setTimeout(x, ms);
    });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn-start">start</button>
<button id="btn-pause">pause</button>
<button id="btn-reset">reset</button>

【问题讨论】:

    标签: javascript jquery arrays loops settimeout


    【解决方案1】:

    这可以通过使用 setInterval() 和生成器函数来实现,而不是使用 for 循环。

    请参考下面的 sn-p 工作示例:

    $(document).ready(() => {
        let loop = loopMoves();
        $('#btn-start').click(() => {
          console.log("****STARTED*****");
          loop("PLAY");
        });
        $('#btn-pause').click(() => {
            console.log("****PAUSED*****");
            loop("PAUSE");
        });
        $('#btn-reset').click(() => {
          console.log("****RESET*****");
          loop("RESET");
        });
    })
    
    const moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
    
    function loopMoves() {
        let moves = generateMoves();
        let intervalId;
          function startInterval(){
            intervalId = setInterval(()=>{
            const move = moves.next();
              if(move.done) 
                clearInterval(intervalId);
              else
                console.log(move.value);
            }, 2000);
          }
        return (state) => {
          switch(state){
            case "PLAY": startInterval(); break;
            case "PAUSE": clearInterval(intervalId); break;
            case "RESET": moves = generateMoves();
                          clearInterval(intervalId); break;
          }
        }
    }
    
    function* generateMoves(){
      for(let i = 0; i < moves.length; i++){
        yield moves[i];
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <button id="btn-start">start</button>
    <button id="btn-pause">pause</button>
    <button id="btn-reset">reset</button>

    【讨论】:

      【解决方案2】:

      您可能会考虑将事物翻转为更多基于时间的“状态机”。

      点击播放时,您将启动计时器(“正在播放”状态),在计时器结束时检查当前状态。如果状态仍在“播放”,则抓住下一步并显示并再次启动计时器。如果没有发生交互,则重复此操作,直到完成所有移动并且状态变为“停止”。 如果有人单击暂停,您将进入“暂停”状态,但其余部分保持原样。当再次单击播放时(从“暂停”到“播放”),您只需回到原来的位置。单击停止时,您将重置所有内容。当从“停止”状态单击播放时,您将移动索引设置为 0 并执行第一步,启动计时器。

      这确实意味着每次更新仅每 2 秒发生一次,但这可能是可以接受的,并且比暂停计时器等替代方案要容易得多。

      const moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4']; // These can be loaded however
      let state = "stopped"; // The starting state
      let currentMoveIndex = 0; // Used to track which move to grab for the next tick
      
      $(document).ready(() => {
          $('#btn-start').click(() => {
              state = "playing";
              tick();
          });
          $('#btn-pause').click(() => {
              state = "paused";
          });
          $('#btn-reset').click(() => {
              state = "stopped";
              tick(); // Since we put the clean up inside the tick function we run it again to be sure it's executed in case it was paused
          });
      })
      
      function tick() {
          switch(state) {
              case "playing":
                  if (currentMoveIndex + 1 === moves.length) {
                      break;
                  }
      
                  const move = moves[currentMoveIndex];
                  console.log(move); // Or whatever you wish to do with the move
                  currentMoveIndex += 1;
                  setTimeout(tick, 2000);
                  break;
              case "paused":
                  // Do whatever you want based on entering the paused state,
                  // Maybe show some message saying "Paused"?
                  break;
              case "stopped":
                  currentMoveIndex = 0;
                  break;
          }
      }
      

      如果您使用 React 或 Angular 之类的东西,您可以将其包装到 Component/Controller 中以将它们组合在一起,或者如果您使用纯 JavaScript,则可以将它们全部包装到 Game 函数中

      【讨论】:

        【解决方案3】:

        我已经尝试了这些答案,它们似乎都有效,谢谢。我回到这个问题是因为我之前想到了一些东西,并希望得到一些反馈。如果我将循环项目存储在一个新数组中并从最后存储的项目设置i = done.lengthdone 是循环项目数组)的位置重新启动循环会怎样。当点击 pause 按钮 clearTimeout() 被调用,当点击 reset 按钮时,我清除了 @ 987654324@数组调用clearTimeout()后,开始仍然运行包含循环的函数。

        我知道我并没有真正暂停计时器,而是停止它并从数组中的最后一个位置重新启动它。这会被认为是“不好的做法”,还是这也是一个可以接受的解决方案?

        $(document).ready(() => {
            $('#btn-start').click(() => {
                loopMoves();
            });
            $('#btn-pause').click(() => {
                clearTimeout(timer);
            });
            $('#btn-reset').click(() => {
                clearTimeout(timer);
                done = [];
            });
        })
        
        var moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
        var done = [];
        
        async function loopMoves() {
            for(let i = done.length; i < moves.length; i++){
                console.log(moves[i]);
                done.push(moves[i]);
                await delay(2000);
            }
        }
        
        var timer;
        function delay(ms) {
            return new Promise((x) => {
                timer = setTimeout(x, ms);
            });
        }

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-12-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-21
          • 1970-01-01
          相关资源
          最近更新 更多