Is there a more accurate way to create a Javascript timer than setTimeout?

后端 未结 16 1992
礼貌的吻别
礼貌的吻别 2020-11-22 14:11

Something that has always bugged me is how unpredictable the setTimeout() method in Javascript is.

In my experience, the timer is horribly inaccurate in

相关标签:
16条回答
  • 2020-11-22 14:29

    JavaScript timeouts have a defacto limit of 10-15ms (I'm not sure what you're doing to get 200ms, unless you're doing 185ms of actual js execution). This is due to windows having a standard timer resolution of 15ms, the only way to do better is to use Windows' higher resolution timers which is a system wide setting so can screw with other applications on the system and also chews battery life (Chrome has a bug from Intel on this issue).

    The defacto standard of 10-15ms is due to people using 0ms timeouts on websites but then coding in a way that assumes that assumes a 10-15ms timeout (eg. js games which assume 60fps but ask 0ms/frame with no delta logic so the game/site/animation goes a few orders of magnitude faster than intended). To account for that, even on platforms that don't have windows' timer problems, the browsers limit timer resolution to 10ms.

    0 讨论(0)
  • 2020-11-22 14:31

    Here's an example demoing Shog9's suggestion. This fills a jquery progress bar smoothly over 6 seconds, then redirects to a different page once it's filled:

    var TOTAL_SEC = 6;
    var FRAMES_PER_SEC = 60;
    var percent = 0;
    var startTime = new Date().getTime();
    
    setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
    
    function updateProgress() {
        var currentTime = new Date().getTime();
    
        // 1000 to convert to milliseconds, and 100 to convert to percentage
        percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;
    
        $("#progressbar").progressbar({ value: percent });
    
        if (percent >= 100) {
            window.location = "newLocation.html";
        } else {
            setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
        }                 
    }
    
    0 讨论(0)
  • 2020-11-22 14:34

    Here are what I use. Since it's JavaScript, I will post both my Frontend and node.js solutions:

    For both, I use the same decimal rounding function that I highly recommend you keep at arms length because reasons:

    const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)
    

    places - Number of decimal places at which to round, this should be safe and should avoid any issues with floats (some numbers like 1.0000000000005~ can be problematic). I Spent time researching the best way to round decimals provided by high-resolution timers converted to milliseconds.

    that + symbol - It is a unary operator that converts an operand into a number, virtually identical to Number()

    Browser

    const start = performance.now()
    
    // I wonder how long this comment takes to parse
    
    const end = performance.now()
    
    const result = (end - start) + ' ms'
    
    const adjusted = round(2, result) // see above rounding function
    

    node.js

    // Start timer
    const startTimer = () => process.hrtime()
    
    // End timer
    const endTimer = (time) => {
        const diff = process.hrtime(time)
        const NS_PER_SEC = 1e9
        const result = (diff[0] * NS_PER_SEC + diff[1])
        const elapsed = Math.round((result * 0.0000010))
        return elapsed
    }
    
    // This end timer converts the number from nanoseconds into milliseconds;
    // you can find the nanosecond version if you need some seriously high-resolution timers.
    
    const start = startTimer()
    
    // I wonder how long this comment takes to parse
    
    const end = endTimer(start)
    
    console.log(end + ' ms')
    
    0 讨论(0)
  • 2020-11-22 14:39

    Dan, from my experience (that includes implementation of SMIL2.1 language in JavaScript, where time management is in subject) I can assure you that you actually never need high precision of setTimeout or setInterval.

    What does however matter is the order in which setTimeout/setInterval gets executed when queued - and that always works perfectly.

    0 讨论(0)
  • 2020-11-22 14:41

    You need to "creep up" on the target time. Some trial and error will be necessary but in essence.

    Set a timeout to complete arround 100ms before the required time

    make the timeout handler function like this:

    calculate_remaining_time
    if remaining_time > 20ms // maybe as much as 50
      re-queue the handler for 10ms time
    else
    {
      while( remaining_time > 0 ) calculate_remaining_time;
      do_your_thing();
      re-queue the handler for 100ms before the next required time
    }
    

    But your while loop can still get interrupted by other processes so it's still not perfect.

    0 讨论(0)
  • 2020-11-22 14:42

    To my experience it is lost effort, even as the smallest reasonable amount of time I ever recognized js act in is around 32-33 ms. ...

    0 讨论(0)
提交回复
热议问题