0

I'm using the plugin code posted at:

jQuery Tips and Tricks

... which is essentailly an asynchronous timer loop:

jQuery.forEach = function (in_array, in_pause_ms, in_callback)
{
    if (!in_array.length) return; // make sure array was sent

    var i = 0; // starting index

    bgEach(); // call the function

    function bgEach()
    {
        if (in_callback.call(in_array[i], i, in_array[i]) !== false)
        {
            i++; // move to next item

            if (i < in_array.length) setTimeout(bgEach, in_pause_ms);
        }
    }

    return in_array; // returns array
};


jQuery.fn.forEach = function (in_callback, in_optional_pause_ms)
{
    if (!in_optional_pause_ms) in_optional_pause_ms = 10; // default

    return jQuery.forEach(this, in_optional_pause_ms, in_callback); // run it
};

With the help of this great community, I have learned that if I "return true;" or "return false;" it has the same affect of moving on to the next iteration of the loop.

What I'd like to do is also programmatically kill (destroy) the $.forEach loop call entirely, not allowing the remaining iterations to run.

How might I add a method to this plugin to allow me to kill a specific call to the plugin while not affecting other calls concurrently running calls?

I've thought about assigning the call to a variable like so:

var foo = $.forEach(json, 1000, function(idx,item) { /* do stuff */ });

...to reference specific instances later without affecting others, but how do I add a kill method and invoke it on foo? I need the equivalent of foo.abort() that's used with ajax calls.

Can it be done? If so, how?

Community
  • 1
  • 1
Inator
  • 994
  • 2
  • 14
  • 33

2 Answers2

0

This is really just example code, so it has some set of issues.

You could alleviate this by simply adding an expando property to the in_array object:

jQuery.forEach = function (in_array, in_pause_ms, in_callback)
{
    // Note, you should use $.isArray(), which is smarter than checking length.
    if (!$.isArray(in_array)) return false;
    in_array.valid = true;
    var i = 0; // starting index
    function bgEach()
    {
        if (!in_array.valid) return;
        if (in_callback.call(in_array[i], i, in_array[i]) !== false)
        {
            i++; // move to next item

            if (i < in_array.length) setTimeout(bgEach, in_pause_ms);
        }
    }

    // I moved this because you were calling it before it was defined.
    bgEach(); // call the function
    return in_array; // returns array
};

Since you're getting the raw array object back from the method, you can just set the 'valid' property of the object to false when you're done:

var foo = $.forEach(json, 1000, function(idx,item) { /* do stuff */ });
foo.valid = false;

Since the valid property cancels the setTimeout recursion, this will effectively kill the looping.

Note that this kind of code is somewhat dangerous. You'll run into race conditions and whatnot, but that's what happens when you start doing pseudo-threading & async code in JS.

John Green
  • 13,241
  • 3
  • 29
  • 51
  • @ John Green - can you explain the race conditions comment? I implemented this to avoid the UI lock-ups associated with $.each or for loops, but would like to know what the dangers are in using this approach. thanks much. – Inator Apr 13 '12 at 02:42
  • Well, you are running a queue of items, and you're only calling them one at a time. If you have to go back through your array for some other function, you need may wind up in a situation where you toggle the state in one place, but it is already queued to be toggled another way in your async array. Generally, your first order of business should be to optimize your queries & closures, although it is always possible that you'd need a queue like this. In my experience though, I've found that 99% of people looking to implement something like this should really just optimize their code instead. – John Green Apr 13 '12 at 09:09
-1

You could pass the timer into the callback and change

if (i < in_array.length) setTimeout(bgEach, in_pause_ms);

to

if (i == in_array.length) clearTimeout(timeout);

This will guarantee that it will only loop as many times as there are items.

Here is the jQuery forEach:

jQuery.forEach = function (in_array, in_pause_ms, in_callback)
{
    if (!in_array.length) return; // make sure array was sent

    var i = 0; // starting index

    bgEach(); // call the function

    function bgEach(){
        var timeout;
        if (in_callback.call(in_array[i], i, in_array[i], timeout = setTimeout(bgEach, in_pause_ms)) !== false)
        {
            i++; // move to next item
            // if end of array, stop iterating
            if (i == in_array.length) clearTimeout(timeout);
        }
    }

    return in_array; // returns array
};

You could use this to clear the timer if it met certain criteria, thereby ending the loop. Example:

$.forEach([1,2,3], 1000, function(idx, item, timer) {
  if(item == 2) {
    clearTimeout(timer);
  }
});
grant
  • 142
  • 4