可以缩短setTimeout的实际延迟吗?

时间:2018-06-18 14:33:23

标签: javascript node.js time

根据documentation,setTimeout的实际延迟可能比问题要长。你能不能给我指点文件或问题的答案,这可以解释setTimeout的实际延迟是否比所要求的更短?

事情是我遇到了一个很少发生的问题,可以用这种现象来解释。平台:Chrome版本67,NodeJS版本9.8.0。另外,我真的好奇这个说法是否适用于Firefox和其他浏览器?

4 个答案:

答案 0 :(得分:4)

您可以通过取两个微时间的增量来测试超时或间隔精度。在我的测试中,定时器加上或minus 几毫秒。运行下面的程序,在您自己的环境中查看结果。

在一个完美的世界中,输出总是显示1000。输出的方差意味着我们的世界是不完美的

var last = Date.now()
var interval = setInterval(function() {
  var now = Date.now()
  var delta = now - last
  console.log(delta)
  last = now
}, 1000)

setTimeout(clearInterval, 10000, interval)
// 1000
// 1003
// 998
// 1002
// 999
// 1007
// 1001
// ... 

要显着影响结果,请按运行,切换到另一个标签页,然后在几秒钟后返回此标签页。您会看到去焦点的标签具有极高的差异。

// 1004  <-- start experiment
// 997
// 1000  <-- switch to another tab
// 1533  <-- variance spikes immediately
// 866
// 1033
// 568   <-- switch back to this tab
// 1001  <-- variance restabilizes
// 1000
// 999

我不知道所有在影响JavaScript中超时和间隔的准确性方面起作用的事情,但我也不认为这是一件重要的事情。最终,我们不需要精确度,因为我们可以使用上面的delta技术计算精确的持续时间。

React中的实际示例

下面我们制作一个简单的Timer组件,天真地使用setInterval每秒刷新一次计时器的显示...

class Timer extends React.Component {
  constructor (props) {
    super (props)
    this.state = { seconds: 0, timeout: null }
  }

  componentDidMount () {
    this.setState ({
      timeout: setInterval (this.tick.bind(this), 1000)
    })
  }
  
  componentWillUnmount () {
    clearTimeout (this.timeout)
  }
  
  tick () {
    this.setState ({ seconds: this.state.seconds + 1 })
  }
  
  render () {
    return <div>Naive timer: {this.state.seconds}</div>
  }
}

ReactDOM.render
  ( <Timer />
  , document.getElementById ('timer')
  )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="timer"></div>

但是由于JavaScript定时器的不可靠性,我们知道我们的Timer组件最终会显示不正确的值。

当我们在下面实施PreciseTimer时,我们可以使用delta技术来确保组件始终显示正确的持续时间

class PreciseTimer extends React.Component {
  constructor (props) {
    super (props)
    this.state = { start: Date.now (), seconds: 0, timeout: null }
  }

  componentDidMount () {
    this.setState ({
      timeout: setInterval (this.tick.bind(this), 1000)
    })
  }
  
  componentWillUnmount () {
    clearTimeout (this.timeout)
  }
  
  tick () {
    const delta = Date.now () - this.state.start
    this.setState ({ seconds: delta / 1000 })
  }
  
  render () {
    return <div>Precise timer: {this.state.seconds}</div>
  }
}

ReactDOM.render
  ( <PreciseTimer />
  , document.getElementById ('timer')
  )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="timer"></div>

要查看这两个计时器的行为方式的实际差异,请同时启动它们,切换到新选项卡10-15秒,然后切换回此选项卡。幼稚Timer将受到JavaScript计时器差异的影响,而PreciseTimer将始终显示正确的持续时间。

答案 1 :(得分:1)

实际上,setTimeout只会在指定的延迟之后将回调推送到事件循环之上,但是因为Javascript是单声道进程,如果阻止事件循环,回调将被延迟。但它可能会缩短或者是使用的代码/ javascript引擎中的错误。

答案 2 :(得分:1)

Javascript的单个执行线程意味着有时候你排队等待将来在x点执行的事情可能实际上是按时执行x +,因为可能没有执行时间帧可用。 然而,事情永远无法在所需时间之前执行,只能在给定时间之前或之后执行

例如,setTimeout(myFunc,100)可以在100毫秒后执行,或者稍微长一点。 您可以在本文中找到有关javascrip的计时器的更多信息:https://johnresig.com/blog/how-javascript-timers-work/

答案 3 :(得分:1)

要完成user633183's answer,这里有一个计时器可确保您的功能从未在给定时间间隔之前运行:

class Timer {
    constructor() {
        this.last = Date.now();
        this.interval = null;
    }

    start (f, ms) {
        // Stop previous timer (if any)
        this.stop();
        // Start new timer
        this.interval = setInterval( () => {
            const now = Date.now();
            // Don't execute before given time interval
            if (now - this.last < ms)
                return;
            this.last = now;
            f();
        }, ms);
    }

    stop () {
        if (this.interval)
            clearInterval(this.interval);
        this.interval = null;
    }
}


const timer = new Timer();
timer.start( () => console.log(Date.now()), 1000);