2

setTimeout(func,delay) seems to fire very precisely at the specified time, as long as the page is not running some script while it is trying to fire the function. But is there a way to take lag into account?

For example if I set a 3sec timeout and javascript runs some heavy code which makes the page leggy for a while. As long as processing the "heavy code" is done by the 3sec it will timeout after ~3sec.

Is there a way to take the "heavy code" processing time into account and timeout after 3sec + the time the page was blocked?

Here is a jsfiddle: http://jsfiddle.net/me2loveit2/mCj2J/

var timeStart = new Date().getTime();
setTimeout(test, 3000); //<-- timeout should be 100

function test() {
    var timeAfter100MS = new Date().getTime();
    $('body').append('Timeout Fired at: <br>' + (timeAfter100MS - timeStart) + 'ms<br> (should be ~3000, but it did not take the blocked time into account.)');
}

function block() {
    for (var i = 0; i < 100000000; i++) {};
}
block();
block();
block();

var timeEnd = new Date().getTime();
$('body').append('Page was blocked(running importaint code :)) for:<br>' + (timeEnd - timeStart) + 'ms<br>');
Philipp Werminghausen
  • 1,142
  • 11
  • 29
  • 3
    No there is not, and that's why javascript timers aren't very accurate. requestAnimationFrame is more accurate, but also a little more complicated to use for a simple timer. Most of the time it doesn't really matter if the timeout is exactly 3 seconds, or 3.2 etc. – adeneo Jul 22 '14 at 15:35

5 Answers5

2

As @adeneo pointed out, there is no such possibility. You simply can't know how effectively processor is running your code at the other end, or the tasks it is currently making which might slow it down further. Every case is different. setTimeout tries to match the specified time but very often, it just can't be exact.

I think the solution is just to change your mindset. Try to avoid long blocking synchronous operations such as for (var i = 0; i < 10000000; i++) {}; When you drop or modify these you can have more accurate setTimeout firing. The reason being, that there will be smaller executable chunks in the event queue.

Generally speaking, there are different ways to do processing of blocking events. For instance, you could look into Web workers or yielding setTimeout calls. (See links at the end of this post).

Hence, I don't know your specific case, but if you are trying to make many setTimeout calls just as in game programming (loops) solution is to try to alter future setTimeout calls to contain smaller value so the full loop will try to catch up the simulation to match the specific frame rate.

This is usually done with combination of requestAnimationFrame.

Short example of a loop which attemps to run 30 fps in the browser:

You can also view it in js fiddle

/**
 * This is example to run loop with 30fps in the browser
 *
 */

var gl = {
    now: new Date().getTime(),
    dt: 0.0,
    last: new Date().getTime(),
    // physics with 0.033333 steps
    step: 1 / 30
},
    frames = 0,
    started = new Date().getTime();

/**
  * Game loop
  *
  */
var gameLoop = function () {

    gl.now = new Date().getTime();
    gl.dt = gl.dt + Math.min(1, (gl.now - gl.last) / 1000);

    while (gl.dt > gl.step) {
        gl.dt = gl.dt - gl.step;

        // Increase frames
        frames++;

        if(frames === 30) {

            // How long it took to execute 30 frames in 1000 ms ?
            document.body.innerHTML = "We executed 30 frames in " + (new Date().getTime() - started) + " ms.";
            started = new Date().getTime();
            frames = 0;

        }  

    }

    // last
    gl.last = gl.now;

    // next
    requestAnimationFrame(gameLoop);

};

// Start the game loop
gameLoop();

Hopefully, this gave you some ideas. Thus, don't forget to use css transitions and similar when those can be applied.

For further reading, I recommend:

Cheers.

Community
  • 1
  • 1
Mauno Vähä
  • 9,688
  • 3
  • 33
  • 54
0

Not sure I understand the question 100%, but if you do something like this, you're able to see if the other stuff (heavy processing) is not done by the time the timeout runs. This should take about 5 seconds, if you switch the 2000000000 to a 20000 (less proccessing), it should come back at 3 seconds.

var timeStart = new Date().getTime();
setTimeout(test, 3000); //<-- timeout should be 100
var currentTime = new Date().getTime();

function test() {
    if (currentTime - timeStart < 3000){
    var timeAfter100MS = new Date().getTime();
        $('body').append('Took less than 3 seconds - ' + (timeAfter100MS - timeStart)+"ms");
    }else{
            $('body').append('Took more than 3 seconds');
    }
}

function block() {

    for (var i = 0; i < 10000000; i++) {};
      currentTime = new Date().getTime();

}
block();
block();
block();


var timeEnd = new Date().getTime();
$('body').append('Page was blocked(running importaint code :)) for:<br>' + (timeEnd - timeStart) + 'ms<br>');
Daryl H
  • 624
  • 8
  • 8
0

If the 'important' code causes really significant lag, and precise timing is important, you can keep the precision by using two timeouts. The first timeout measures the lag and sets the second timeout accordingly.

Here's an example, using your code as a basis:

var timeStart = new Date().getTime();
var msDelay = 3000;
setTimeout(testLag, msDelay - 500);

function testLag() {
    var timeTestLag = new Date().getTime();
    $('body').append('testLag() fired at: ' + (timeTestLag - timeStart) + 'ms<br/>');
    setTimeout(test, timeStart + msDelay - timeTestLag);
}

function test() {
    var timeAfter100MS = new Date().getTime();
    $('body').append('Timeout Fired at: <br>' + (timeAfter100MS - timeStart) + 'ms<br> (should be ~3000, but it did not take the blocked time into account.)');
}

function block() {
    for (var i = 0; i < 1000000000; i++) {};
}
block();
block();
block();
block();
block();

var timeEnd = new Date().getTime();
$('body').append('Page was blocked(running importaint code :)) for:<br>' + (timeEnd - timeStart) + 'ms<br>');

Note that the block is significantly more intensive than yours - I added a zero to your 100000000, and added a couple of extra block() calls. You might need to adjust the figures to get a sensible level of block for your own machine.

Nanki
  • 798
  • 7
  • 9
  • Adjusting figures for own machine is not really a way of solving this; even your own machine can appear to be slow or fast from time to time. You can't know the lag which can be caused by almost anything between the setTimeout call and the resulting action which should be triggered in the future. The only point you know the lag is when the action is already happened. – Mauno Vähä Jul 22 '14 at 17:13
  • I adjusted the figures to recreate the issue, not to solve the problem. The testLag() call was the attempt to improve the accuracy. – Nanki Jul 22 '14 at 17:46
0

Based on Mauno's Answer I came up with a solution to temporarily "track the lag" using an interval. I am setting an interval with short intervals to capture the delay and set another timeout if necessary. Here is the working example: http://jsfiddle.net/me2loveit2/mCj2J/14/

It is approximate, but always walls within 100ms of the target which is good enough for me. It could be even more accurate if I increase the interval rate, but what I got is good enough for me.

I know using timeout & interval is not the best but sometimes the only way. I am just using them for a couple of seconds on page load and that's it.

Here is the code:

var timeStart = new Date().getTime();
var aditionalTimeout = 0;
var myTimeout;
setTimer(3000);
block();
block();
block();
var timeEnd = new Date().getTime();
$('body').append('Page was blocked(running importaint code :)) for:<br>' + (timeEnd - timeStart) + 'ms<br>');

function setTimer(milliseconds) {
    //allow additional time to account for the huge lag the page has on load
    recoverLagTime(milliseconds);
    myTimeout = setTimeout(function () {
        if (!aditionalTimeout) {
            test();
        } else {
            if (aditionalTimeout >= milliseconds) {
                test();
                return;
            }
            setTimer(aditionalTimeout);
        }
    }, milliseconds);
}

function recoverLagTime(timeoutTime) {
    aditionalTimeout = 0;
    var interval = 50;
    var counter = Math.ceil(timeoutTime / interval);
    var startTime = new Date().getTime();
    var intervalTime;
    var lagInterval = setInterval(adjustTimer, interval);

    function adjustTimer() {
        if (counter <= 0 || aditionalTimeout < 0) {
            clearInterval(lagInterval);
            return;
        }
        counter--;
        intervalTime = new Date().getTime();
        var diff = (intervalTime - startTime);
        if (diff > (interval + 5)) {
            aditionalTimeout += (diff - interval);
        }
        startTime = new Date().getTime();
    }
}

function test() {
    aditionalTimeout = -100;//stop the check function
    var timeAfter100MS = new Date().getTime();
    $('body').append('Timeout Fired at: <br>' + (timeAfter100MS - timeStart) + 'ms<br> (should be ~3000 + ~amount blocked)');
}

function block() {
    for (var i = 0; i < 100000000; i++) {};
}
Philipp Werminghausen
  • 1,142
  • 11
  • 29
  • This looks complicated, for instance. if you simplify your code to contain normal setTimeout call it can be more accurate than this: http://jsfiddle.net/mCj2J/15/ At least, it was for me (100ms vs 1ms). This is the reason why my answer still stands. Don't try to over-complicate this, just avoid long blocking synchronous operations and re-think your programming logic. – Mauno Vähä Jul 23 '14 at 13:58
  • Well unfortunately I don't have control over the long blocking synchronous code, else I would do something about it. – Philipp Werminghausen Jul 23 '14 at 14:56
0

I wrote a small (2-file) library for exactly these purposes : running heavy code whenever the CPU has idle time (using requestAnimationFrame) by splitting the code into smaller iterations so it doesn't block the whole application by allotting a specific percentage of CPU time to execution the code, and use the remainder to execute other scripts / update UI.

It functions similarly to other answers, but might be convenient to you as you can easily calculate elapsed time between executions, if you need to know these figures (or use it to leverage operations within your application, as that's what it was written for)

https://github.com/igorski/zThreader

Igor Zinken
  • 884
  • 5
  • 19