2

I several divs on my site, I'd like to update one by one. In order not to spam the server with 200+ requests at once, I'd like to delay them by 1s each.

What I tried:

var $tourBox = $('.tour-box');
$tourBox.each(function () {
    var $box = $(this);
    setTimeout(function () {
        getUpdate($box);
    }, 1000);
});

The update function:

function getUpdate($box) {

    var $so = $box.attr('data-so');
    var $url = $('#ajax-route-object-status').val();
    $.get($url, {
        so: $so
    }).success(function (data) {
        var $bg = 'bg-gray';
        if (data.extra.isStarted === true && data.extra.isFinished === false) {
            $bg = 'bg-orange'
        }
        if (data.extra.isStarted === true && data.extra.isFinished === true) {
            $bg = 'bg-green'
        }
        if (data.extra.isLate === true && data.extra.isFinished === false) {
            $bg = 'bg-red'
        }
        $box.removeClass('bg-gray').removeClass('bg-green').removeClass('bg-orange').removeClass('bg-red').addClass($bg);
    });
}

In Chrome Dev--> Network it shows the all loaded as pending and then loads them one by one, but w/out the delay:

ChromeDevScreenshot

As you can see between 3907 and 3940, there is merely half a second delay. This doesn't change even if I have a timeout of 5000.

PrimuS
  • 2,505
  • 6
  • 33
  • 66
  • what do you mean ' by 1s each.'? do you mean every request will be made with one second interval, or that all request will be made after 1s? Cause that's what you code is doing. `setTimeout` does not pause the javascript execution. What your code is doing is its setting all the get request after one second – j4rey Jul 21 '17 at 09:06
  • Its working as expected callback are suppose to work like this. Please take a look at http://prashantb.me/javascript-call-stack-event-loop-and-callbacks/ for better understanding of how callback functions works. – ArunBabuVijayanath Jul 21 '17 at 09:20

3 Answers3

2

Back in 2008 I wrote a slowEach() plugin for jQuery that does what you're looking for. It's basically a drop-in replacement for $.each() and $(...).each() that takes a time interval, so the callback is called with that amount of delay for each element:

jQuery.slowEach = function( array, interval, callback ) {
    if( ! array.length ) return;
    var i = 0;
    next();
    function next() {
        if( callback.call( array[i], i, array[i] ) !== false ) {
            if( ++i < array.length ) {
                setTimeout( next, interval );
            }
        }
    }
};

jQuery.fn.slowEach = function( interval, callback ) {
    jQuery.slowEach( this, interval, callback );
};

With that code, you can then do:

$('.tour-box').slowEach( 1000, function() {
    getUpdate( $(this) );
});

One thing to note about this code is that it uses only a single timer at a time, instead of making hundreds of setTimeout() calls to fire up multiple timers at once. This makes it much easier on system resources.

Michael Geary
  • 28,450
  • 9
  • 65
  • 75
1

Your for each is calling all of the timeouts at once. It is not waiting one second in between calling each time out. So you have thousands of objects that are being scheduled to call getUpdate($box); after one second.

What you can do is increase the timeout in each iteration.

var $tourBox = $('.tour-box');
var delay = 1000;
$tourBox.each(function () {
    var $box = $(this);
    setTimeout(function () {
        getUpdate($box);
    }, delay);
    delay += 1000;
});

This will cause your first timeout to be fired after 1 second, and your second after two seconds and so on.

Alexander Higgins
  • 6,765
  • 1
  • 23
  • 41
  • That will work for a smallish array, but you should be cautious about making a potentially large number of `setTimeout()` calls. It's just as easy to do this with only one `setTimeout()` active at any one time. Instead of starting up a large number of `setTimeout()` calls with an increasing delay, simply use the same delay for each call but start a new `setTimeout()` after you process the previous one. – Michael Geary Jul 21 '17 at 09:40
1

Calling one by one could be another solution to avoid spam like request. In that case, this could be a solution:

$(document).ready(function(){

    var $tourBox = $('.tour-box'),
        curIndex = 0,
        totalBox = $tourBox.length,
        url = $('#ajax-route-object-status').val(),


    function getUpdate(){

        var $box = $tourBox.get( curIndex );

        if ( typeof $box === 'undefined'){
            // No more box to process
            return; // exit
        }

        var $so = $box.attr('data-so');

        $.get($url, {
            so: $so
        }).success(function (data) {
            var $bg = 'bg-gray';
            if (data.extra.isStarted === true && data.extra.isFinished === false) {
                $bg = 'bg-orange'
            }
            if (data.extra.isStarted === true && data.extra.isFinished === true) {
                $bg = 'bg-green'
            }
            if (data.extra.isLate === true && data.extra.isFinished === false) {
                $bg = 'bg-red'
            }
            $box.removeClass('bg-gray').removeClass('bg-green').removeClass('bg-orange').removeClass('bg-red').addClass($bg);
        }).always(function(){

            // Increment index to process
            curIndex++;

            // Finished either with success or failed
            // Proceed with next
            getUpdate();
        });       
    }
});
Amit Bhagat
  • 4,182
  • 3
  • 23
  • 24