1

I have a progressbar on the top of my page that I'm using to display the 1 minute timer used to refresh all the charts. The problem I'm having is that it never seems to be consistent.

function headerBar() {
    var progressbar = $('#timerBar');
    progressbar.progressbar({
        value: 0
    });

    function progress() {
        var val = progressbar.progressbar('value') || 0;
        progressbar.progressbar('value', val + 5);
        if (val < 99) {
            setTimeout(progress, 3000);
        }
    }
    progress();
}
headerBar();
  setInterval(function () {
     headerBar();
  }, 60 * 1000);

The first time it runs fine, but the 2nd time seems to take 1/2 as long and every time after that seems to shave off time from the count.

here's the fiddle

Adil Shaikh
  • 44,509
  • 17
  • 89
  • 111
jbolanos
  • 615
  • 3
  • 9
  • 20
  • put your setInterval function after the call to the function progress in the headerbar function – Rachel Gallen May 24 '13 at 17:11
  • The setInterval function runs all the code to get the JSON and update the charts so it needs to be stand alone. I need the headerBar() function to reset the timer completely so the setTImeout function doesn't stack. – jbolanos May 24 '13 at 17:14

3 Answers3

3

Hurray! You're running into a kind-of race condition. Since setTimeout() does not guarantee to call every 3000ms like setInterval() (and even if it did, you'd still be subject to this problem), what most likely happens is that your interval calls headerBar() before progress() manages to stop itself from adding more timeouts! Here's a timeline to clear this up:

Your progressbar is at 95!

timeout1 -> I better call progress()!

progress() -> Set progress bar to val + 5 = 100! val(95) < 99 ? Yeah! I better set a timeout to call myself again

interval -> Thy time is up! headerBar()!

headerBar() -> I set the progress to 0, and start more progress()!

progress() -> Set progress bar to 5! Timeout to call myself again!

progress() -> Set progress bar to 10! Timeout to call myself again! (Oops, I never got to val > 99 to stop calling myself repeatedly!)

time passes...

interval -> It's time to go FASTER! headerBar()!

But how do you solve this problem? Well, firstly, you'll want to change your progress() check to use val + 5 < 99. That still won't save you from problems like calling 20 timeouts taking longer than your one interval - you'll need some way to signal your progress() to stop with all the progressing.

One way to do this would be to add some sanity checks to headerBar():

function headerBar() {
    var progressbar = $('#timerBar');
    var lastProgress = 0;
    progressbar.progressbar({
        value: lastProgress
    });

    function progress() {
        var val = progressbar.progressbar('value') || 0;
        if (val != lastProgress) {
            // Someone's modified the bar! I best stop before I do too much progress!
            return;
        }
        progressbar.progressbar('value', val + 5);
        if (val + 5 < 99) {
            setTimeout(progress, 3000);
        }
    }
    progress();
}

This is a very hacky solution, however. A much more elegant one would be to use a closure!

var headerBar = (function () {
    var inProgress = false;
    var progressbar = $('#timerBar'); // Save some trees by calling jQuery selection only once!
    return function headerBar() {
        if (inProgress) {
            clearTimeout(inProgress);
            inProgress = false;
        }
        progressbar.progressbar({
            value: 0
        });
        function progress() {
            var val = progressbar.progressbar('value') || 0;
            progressbar.progressbar('value', val + 5);
            if (val < 99) {
                inProgress = setTimeout(progress, 3000);
            }
        }
        progress();
    }
})();

In general though, you could still work on eliminating your interval(this would introduce a slight error in your timing), by modifying headerBar() to simply tell you when it's done.

// Pass the jQuery element so you not only have to select it once, but don't depend on the headerBar knowing where to look for it.
var headerBar = (function (progressbar) {
    return function headerBar(callback) {
        progressbar.progressbar({ value : 0 });
        (function progress() {
            var newVal = (progressbar.progressbar('value') || 0) + 5;
            progressbar.progressbar('value', newVal);
            if (newVal < 99) setTimeout(progress, 3000);
            else callback();
        })();
    }
})($('#timerBar'));

function heavyLifting() {
    // get stuff, etc
    headerBar(heavyLifting);
}
Sacho
  • 2,169
  • 1
  • 14
  • 13
1

Following your comment:

SEE DEMO

(function () {
    var progressbar;

    function headerBar() {
        progressbar = $('#timerBar');
        progressbar.progressbar({
            value: 0
        });

        function progress() {
            var val = progressbar.progressbar('value') || 0;
            progressbar.progressbar('value', val + 5);
            if (val < 99) {
                setTimeout(progress, 3000);
            }
        }
        progress();
    }
    headerBar();
    setInterval(function () {
        progressbar.progressbar('value', 0);
    }, 60 * 1000);
})();
A. Wolff
  • 74,033
  • 9
  • 94
  • 155
  • The setInterval has a lot to do - I need the progress bar to reset when it starts for a new minute. The setInterval isn't just for the progressbar but the progressbar must sync up to what the setInterval is doing. – jbolanos May 24 '13 at 17:20
  • i love js fiddle ! thanks for the code i searched for free countdown timer in jquery and this is what i wanted :) – shareef Jul 19 '13 at 21:29
0

you need a clear interval.. like this

function headerBar() {
    var progressbar = $('#timerBar');
    progressbar.progressbar({
        value: 0
    });

    function progress() {
        var val = progressbar.progressbar('value') || 0;
        progressbar.progressbar('value', val + 5);
        if (val < 99) {
            setTimeout(progress, 3000);
        }
    }
    progress();
    clearInterval()
}
headerBar();
setInterval(function () {
        headerBar();
    }, 60 * 1000);

More examples of setTimeout on SitePoint here

Rachel Gallen
  • 27,943
  • 21
  • 72
  • 81
  • I can't clear the interval because the setInterval is running a mess of code every 60 seconds - I need the progressbar to reset each time the setInterval loops – jbolanos May 24 '13 at 17:20
  • whhy are you calling header bar twice anyway? – Rachel Gallen May 24 '13 at 17:24
  • The first time (before the setInterval) starts the first 60 sec progress - when it finishes is when the first setInterval loop is called. the setInterval doesn't run any of its code until 60 seconds after the page loads so the progressbar needs to show this time too – jbolanos May 24 '13 at 17:26
  • thanks, I needed to come here so someone could tell me I have a problem when I'm asking for help with my problem. – jbolanos May 24 '13 at 17:30
  • have a look at this question i think it will help loads http://stackoverflow.com/questions/7690465/using-settimeout-to-update-progress-bar-when-looping-over-multiple-variables – Rachel Gallen May 24 '13 at 17:47
  • @RachelGallen Please be aware that the documentation link will be non-functional in the near future, which is why I made the edit removing the link. –  Sep 15 '17 at 16:39