在循环中暂停并重新启动 setTimeout

Posted

技术标签:

【中文标题】在循环中暂停并重新启动 setTimeout【英文标题】:Pause and restart setTimeout in loop 【发布时间】: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>

【问题讨论】:

【参考方案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);
    );

【讨论】:

以上是关于在循环中暂停并重新启动 setTimeout的主要内容,如果未能解决你的问题,请参考以下文章

如何在 jquery transit 中暂停和重新启动 css3 转换

如何使用CoffeeScript重新同步setTimeout()? Rails 5

Python打破嵌套的for循环并重新启动while循环

当后台服务取消暂停或重新启动时,iOS 应用程序需要多长时间?

停止 AVPlayer 并重新启动 MP3 文件

当活动从后台(暂停)到前台时重新创建 Android 活动