setInterval and setTimeout delay issues

I've been trying to manage a timer via recursive setTimeout in react-native.

But i'm facing the problem that in some devices the timer is taking some time more in process(like 1-4 seconds in around 100-150 seconds timer).

I've already removed setInterval as it was worse than recursive setTimeout. any ideas that how can i make this timer perfect?

Edit: the main problem is that i ran application(In release mode) in 2 or more devices. the timer starts perfectly but devices seem to have very small delay in them, which is quite increasing by time.

The api calls in app are done parrallely.

Code:

AnotherTimerHandler = () => {
    this.time = setTimeout(() => {
        if (this.state.gameState == timesup) {
            console.log(timesup)
            this.setState({ timer: this.state.timer - 1 });
            if (this.state.timer <= 0) {
                if (this.state.questionIndex < numberOfQuestions - 1) {
                    this.setState({ gameState: splash, timer: splashTime, QAndA: {}, correctAnswer: '', questionIndex: this.state.questionIndex + 1, answered: false })
                } else {
                    // console.log('123')
                    clearInterval(this.time)
                    console.log(this.state.playerMode)

                    if (this.state.playerMode) {
                        const { username, firstName, lastName } = this.props.navigation.state.params.userData;
                        firebase.database().ref(`tblGame/${gameIdToLoad}/gameWinners`).push({ Email: firebase.auth().currentUser.email, Name: firstName + ' ' + lastName })
                            .then(() => this.props.navigation.navigate('Winner', { gameId: gameIdToLoad, prizeAmount: this.props.navigation.state.params.QuizData.prizeAmount }))
                            .catch(err => alert(err))
                    } else { this.props.navigation.navigate('Winner', { gameId: gameIdToLoad, prizeAmount: this.props.navigation.state.params.QuizData.prizeAmount }); }
                }
            }
        }
        else if (this.state.gameState == playing) {
            console.log('playing')
            if (this.state.timer == questionTimer) {
                // console.log('playing1', this.state.timer)
                // this.setState({ answerLoaded: false })
                // this.QAndAHandler(Question)
                this.refs.circularProgress.performLinearAnimation(0, (questionTimer - 1) * 1000)
            }
            this.setState({ timer: this.state.timer - 1 })
            // if (this.state.timer == -1) {
            if (this.state.timer <= 0) {
                this.setState({ gameState: timesup, timer: answerTimer }); this.QAndAHandler(Ans);
                // console.log('playing2', this.state.timer)
            }
        }
        else if (this.state.gameState == splash) {
            console.log(splash)
            console.log(this.state.timer)
            this.setState({ timer: this.state.timer - 1 })
            if (this.state.timer == splashTime - 1) {
                this.QAndAHandler(Question)
            } else if (this.state.timer <= 0) this.setState({ timer: questionTimer, gameState: playing, answerLoaded: false })
        }
        // Dont call again if scren is being changed 
    return this.state.gameState == timesup && this.state.timer<=0 && !(this.state.questionIndex < numberOfQuestions - 1) ? null : this.AnotherTimerHandler()    
    }, 1000)
}
javascriptreact-nativesettimeoutsetinterval

Answers

answered 3 months ago Kaiido #1

"Timing is never guaranteed",
but 4 seconds difference on 150ms interval can indeed be seen as big.

One way to circumvent this is splitting your timings in smaller dynamics intervals, self-correcting its own delay.

Here is a dumb implementation that will reschedule the next tick to the next second, correcting itself its delay at every tick:

// Self-correcting setInterval
// intended for long intervals
// returns an object which "_id" property is the inner timeout id, so it can be canceled by clearInterval
function selfCorrectingInterval(cb, ms) {

  var innerTimeout = ms < 1000 ? 100 : 1000, // fire every ?s
    begin = performance.now(), // what time is it?
    last = begin + ms, // when should all this end?
    next = Math.min(innerTimeout, ms),
    prev = begin,
    result = {
      _id: setTimeout(inner, next)
    },
    passed = true; // a flag to avoid try-catch the callback
  return result;

  function inner() {
    if (!passed) return;
    passed = false; // set up the callback trap

    var shouldCall = false;
    var now = performance.now(),
      delay = (now - prev) - innerTimeout;
    prev += innerTimeout; // fixed increment

    if (last - now < 6) {
      shouldCall = true;
      begin = last; // start a new interval
      last += ms;
    }

    next = Math.min(innerTimeout - delay, last - now);
    result._id = setTimeout(inner, next);
    // call it at the end so we can cancel inside the callback
    if (shouldCall) {
      cb();
    }
    passed = true; // didn't throw we can continue
  }
}

// snippet-only tests
function test(ms) {

  function setTimeoutLoop(cb, ms) {
    function loop() {
      cb();
      setTimeout(loop, ms);
    }
    setTimeout(loop, ms);
  }

  var now = performance.now(),
    built_in_prev = now,
    timeout_prev = now,
    sCI_prev = now,
    built_in_elem = document.querySelector('#test_' + ms + ' .delay.built_in'),
    timeout_elem = document.querySelector('#test_' + ms + ' .delay.timeout'),
    sCI_elem = document.querySelector('#test_' + ms + ' .delay.sCI');

  setInterval(() =>  {
    var now = performance.now(),
      delay = (now - built_in_prev) - ms;
    built_in_prev += ms;
    built_in_elem.textContent = Math.round(delay);
  }, ms);

  setTimeoutLoop(() => {
    var now = performance.now(),
      delay = (now - timeout_prev) - ms;
    timeout_prev += ms;
    timeout_elem.textContent = Math.round(delay);
  }, ms);

  selfCorrectingInterval(() =>  {
    var now = performance.now(),
      delay = (now - sCI_prev) - ms;
    sCI_prev += ms;
    sCI_elem.textContent = Math.round(delay);
  }, ms);

}

test(1000);
test(5000);
test(60000);
test(150000);
[id^='test'] {
  border: 1px solid;
  padding: 0 12px
}
<div id="test_1000">
  <p>built in setInterval delay for 1000ms interval: <span class="delay built_in">0</span>ms</p>
  <p>built in setTimeout loop delay for 1000ms interval: <span class="delay timeout">0</span>ms</p>
  <p>selfCorrectingInterval delay for 1000ms interval: <span class="delay sCI">0</span>ms</p>
</div>
<div id="test_5000">
  <p>built in setInterval delay for 5000ms interval: <span class="delay built_in">0</span>ms</p>
  <p>built in setTimeout loop delay for 5000ms interval: <span class="delay timeout">0</span>ms</p>
  <p>selfCorrectingInterval delay for 5000ms interval: <span class="delay sCI">0</span>ms</p>
</div>
<div id="test_60000">
  <p>built in setInterval delay for 1 minute interval: <span class="delay built_in">0</span>ms</p>
  <p>built in setTimeout loop delay for 1 minute interval: <span class="delay timeout">0</span>ms</p>
  <p>selfCorrectingInterval delay for 1 minute interval: <span class="delay sCI">0</span>ms</p>
</div>
<div id="test_150000">
  <p>built in setInterval delay for 150s interval: <span class="delay built_in">0</span>ms</p>
  <p>built in setTimeout loop delay for 150s interval: <span class="delay timeout">0</span>ms</p>
  <p>selfCorrectingInterval delay for 150s interval: <span class="delay sCI">0</span>ms</p>
</div>

And that's how I discover Chrome's implementation of setInterval already does correct itself...

answered 3 months ago Kunal #2

If you have very small time interval to fire your callback then javascript based timer would not be suitable, but if you have longer time interval to fire your callback then that will work. Try using this, https://github.com/ocetnik/react-native-background-timer Hope this would give better result, precession will be still questionable as it is relative term.

comments powered by Disqus