react-native: [BUG] setTimeout fires incorrectly when using chrome debug

Hey,

I’ve found an issue that means when you’re running on an iOS device and debugging in chrome setTimeouts can fire in the wrong order. If you place the following code block into a fresh app and run it you should get the error…

  componentDidMount:function() {
    this.runTest()
  },

  test:function(cb) {
    var aFired = false
    var bFired = false
    var aExpected = new Date().getTime()+130
    var bExpected = new Date().getTime()+500
    console.log('Schedule A to fire @' + aExpected)
    console.log('Schedule B to fire @' + bExpected)

    var a = setTimeout(() => {
      var now = new Date().getTime()
      console.log('A fired', now, now - aExpected)
      aFired = true
      if (bFired) { console.log('ERROR: b fired early'); return;
      }
      if (aFired && bFired) { cb() }
    }, 130)
    var b = setTimeout(() => {
      var now = new Date().getTime()
      console.log('B fired', now, now - bExpected)
      bFired = true
      if (!aFired) { console.log('ERROR: A not fired'); return;
      }
      if (aFired && bFired) { cb() }
    }, 500)
  },
  runTest:function() {
    this.test(() => {
      setTimeout(() => {
        this.runTest()
      }, 500)
    })
  },

On the console you’ll get something like…

index.ios.js:25 Schedule A to fire @1448977966848
index.ios.js:26 Schedule B to fire @1448977967218
index.ios.js:30 A fired 1448977966754 -94
index.ios.js:38 B fired 1448977966755 -463
index.ios.js:25 Schedule A to fire @1448977966902
index.ios.js:26 Schedule B to fire @1448977967272
index.ios.js:30 A fired 1448977966806 -96
index.ios.js:38 B fired 1448977966806 -466
index.ios.js:25 Schedule A to fire @1448977966951
index.ios.js:26 Schedule B to fire @1448977967321
index.ios.js:30 A fired 1448977966835 -116
index.ios.js:38 B fired 1448977966835 -486


index.ios.js:25 Schedule A to fire @1448977966983
index.ios.js:26 Schedule B to fire @1448977967353
index.ios.js:38 B fired 1448977966871 -482
index.ios.js:40 ERROR: A not fired
index.ios.js:30 A fired 1448977966871 -112
index.ios.js:32 ERROR: b fired early

… with the first 3 firing correctly and the last one incorrectly. I believe this bug is also what is causing issue #1693 as one of the setTimeout functions sometimes fires early in touchableHandleResponderGrant

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 9
  • Comments: 47 (20 by maintainers)

Commits related to this issue

Most upvoted comments

RN 0.50 Same issue

I’ve experienced this while “Debug in Chrome” is active. setTimeout fires very early.

Has it been confirmed that TimerMixin avoids this issue?

I thought TimerMixin only helps with cleanup in componentWillUnmount?

Still an issue in 0.46.3, can we reopen this?

For the people who are still facing this issue ! which is the case for us on RN 0.44.0 on Android :

We found indeed that the problem was related to the device time not being exactly as the time on the chrome remote debugger machine. This is due to the React Native javascript setTimeout function calling the native Timing.createTimer method with a Date.now() parameter which is not the same as the native side’s time when in remote js debug mode (where the javascript execution environment is the chrome browser or whatever debugger you’re using) as you can see in JSTimers.js#L93.

So, the solution for us was 1 of the following :

  • Either set the time of the device to be exactly as the remote debugging machine’s time using ADB
  • Or compensate for the time difference between the native side and the remote js side

We chose the second solution as the first one wasn’t suited for our needs so we created a package called React Native Timer Native (I know, i’m not so good when it comes to naming things ^^') which is a fork of React Native Timer.

What it does --as a stupid hack-- is simply call a native method (when in dev mode) with the current javascript timestamp, then the native method gets its current timestamp using System.currentTimeMillis(), calculates the difference and returns it back to javascript, then the js package just adds that to whatever duration you passed to timer.setTimeout .

The device time must be in the future compared to the remote debugging machine time otherwise it doesn’t work properly

It does get the timeouts executed with a decent precision (still need to substract the time that the bridge takes to call the native method so any inputs are welcome 😉 ).

I’m not certain that the issue that is reported on this thread is resolved. I’m getting the same behavior as described by many of you. One way to get things done and testable was to set the timers to a time less than 2 seconds, but when the app gets bigger it will be unbearable to test it.

RN0.42.0

So, I did a some small test on this,

var start = performance.now(); console.log(performance.now(), start); // 751855.6900000001 492872.2
var start = performance.now(); setTimeout(() => console.log(performance.now() - start), 1); // 345195.46

This is super weird. The value of performance.now() is vastly different than when it’s assigned to a variable. SImilar results for Date.now().

No wonder setTimeout executes instantly even if you give a timeout of 10 seconds.

cc @vjeux @dmmiller @nicklockwood

For me, the setTimeout will wait until a re-render happens. So you have to touch the screen or scroll the page in order to fire the handler (assuming the time has passed). Disabling remote debugging makes it work as normal

Still an issue on 0.47.x as far as I can tell