使用 clearTimeout() 后递归 setTimeout() 循环没有停止

Recursive setTimeout() loop not stopped after using clearTimeout()

我正在制作一个简单的倒数计时器,显示剩余的秒数和秒数。当用户单击按钮时,倒计时应该停止,并且应该显示一些文本而不是计时器。在 PC 上一切正常,但如果我将我的 phone 与更糟糕的 cpu 一起使用,我有时会出现这种意外行为:当用户单击按钮时,文本显示几分之一秒,然后计时器保持继续。我认为这是由函数 运行 时调用 clearTimeout() 方法引起的,就在调用新超时之前,但 post 证明我错了。您知道是什么导致了这种行为吗?我应该如何解决它?

const finished = () => { // finished() is also called when user clicks a button
 // show some thext on the screen
    clearTimeout(timeout);
}

const start = document.timeline.currentTime! // Think of it as Date.now(); also returns a ms value; just slightly faster.
const end = start + 30000 // the countodwn takes 30 seconds
let timeout:any;

const frame = () => {
    const elapsed = Math.floor(end - document.timeline.currentTime!)

    if (elapsed <= 0) { 
        finished()
        return
    };

    let secs: number | string = Math.floor(elapsed / 1000)
    let setsecs: number | string = Math.floor((elapsed % 1000) * 0.1)

    secs = secs < 10 ? "0" + secs : secs
    setsecs = setsecs < 10 ? "0" + setsecs : setsecs

    result.innerText = `Time left: ${secs}.${setsecs}`

    timeout = setTimeout(() => requestAnimationFrame(frame), 15 - elapsed % 15) // raf is for optimisation and I'm substracting the timeout by the time that has already passed for drift minimisation
}

frame()

编辑:我设法在我的 phone 上重新创建了缓慢的行为,并承诺:Here's the code

TL;DR

问题

setTimeoutclearTimeout 之间的时间太短了。在 clearTimeout.

之后触发重新安排

例子

使用布尔标志检查是否调用了 finished 函数。


解决方案

TS Playground

const start = document.timeline.currentTime! // Think of it as Date.now(); also returns a ms value; just slightly faster.
const end = start + 3000 // the countodwn takes 3 seconds
let timeout:any;

let finishedCalled = false
const finished = () => { // finished() is also called when user clicks a button
 // show some thext on the screen
    finishedCalled  = true
    clearTimeout(timeout)

    console.log(`Finished!`)
}

setTimeout(finished, 2000)

const frame = async () => {
    if(finishedCalled) return console.log(`Finished called found. Have to be end here.`)
    const elapsed = Math.floor(end - document.timeline.currentTime!)
    console.log(`Elapsed:`, elapsed)

    if (elapsed <= 0) { 
        finished()
        return
    };

    let secs: number | string = Math.floor(elapsed / 1000)
    let setsecs: number | string = Math.floor((elapsed % 1000) * 0.1)

    secs = secs < 10 ? "0" + secs : secs
    setsecs = setsecs < 10 ? "0" + setsecs : setsecs

    console.log(`Time left: ${secs}.${setsecs}`)

    await new Promise((resolve) => setTimeout(resolve, 500));

    timeout = setTimeout(() => {
        console.log(`Timer fired!`)
        requestAnimationFrame(frame)
    }, 10) // raf is for optimisation and I'm substracting the timeout by the time that has already passed for drift minimisation
}

frame()

深入挖掘

以Node.js为例,追根溯源

到此为止,我们可以知道这是一个异步调用,可以在执行之前重新安排。这就是 finished 函数无法停止 timer.

的原因

Disclaimer: I am not an expert of C++. Please feel free to improve the answer.