Home > Net >  Recursive setTimeout() loop not stopped after using clearTimeout()
Recursive setTimeout() loop not stopped after using clearTimeout()

Time:04-28

I'm making a simple countdown timer that displays the seconds and setseconds left. When an user clicks a button the countdown should stop and some text should be displayed instead of the timer. Everything works fine on pc but if I use my phone with a worse cpu I sometimes get this unexpected behaviour: when the user clicks the button the text shows for a fraction of a second and then the timer keeps going on. I thought it was caused by the clearTimeout() method being called when the function was running and right before a new timeout would be called but this post proved me wrong. Do you have any idea what's causing this behaviour and how should I go about fixing it?

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

EDIT: I managed to recreate the slow behaviour on my phone with promises: Here's the code

CodePudding user response:

TL;DR

Problem

The time is too short between setTimeout and clearTimeout. A rescheduling is fired behind clearTimeout.

Example

Use a boolean flag to check if that finished function had been called.


Solution

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

Dig Deeper

Let's use Node.js as an example and trace down the source.

Until this point, we can know it is an async call and may be rescheduled before execute. That's why the finished function cannot stop the timer.

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

  • Related