4

I am trying to loop over an array. However, I would like to add a 15 second delay between each array value. This will write value 1 to console, then count down 15 seconds and write value 2 to console, and so on.

I'm not sure exactly how to do this. My code as of now just outputs the numbers 15 all the way to 1 on the console at once with no actual count down and no array values.

array

["l3", "l4", "l5", "l6", "l7", "l8", "l9", "l10", "l11", "l12", "l13", "l14", "l15", "l16"] 

code

var adArray = [];
// get links with class adfu
var adfuClass = document.getElementsByClassName('adfu');
for (var i = 0; i < adfuClass.length; i++) {
    var ids = adfuClass[i].id
    var newIds = ids.replace(/tg_/i, "l");
    adArray.push(newIds);
}
// get links with class ad30
var ad30Class = document.getElementsByClassName('ad30');
for (var i = 0; i < ad30Class.length; i++) {
    var ids = ad30Class[i].id;
     var newIds = ids.replace(/tg_/i, "l");
     adArray.push(newIds);
}
// get links with class adf
var adfClass = document.getElementsByClassName('adf');
for (var i = 0; i < adfClass.length; i++) {
    var ids = adfClass[i].id;
     var newIds = ids.replace(/tg_/i, "l");
     adArray.push(newIds);
}
// loop through array with all new ids
for (var i = 0, l = adArray.length; i < l; i++) {
    var counter = 15;
    var countDown = setTimeout(function() {
        console.log(counter);
        if (counter == 0) {
            console.log(adArray[i]);
        }
        counter--;
    }, 1000);
}
Daniel
  • 4,202
  • 11
  • 50
  • 68
  • Look at the `wait()` function in this fiddle I made: http://jsfiddle.net/9hBfs/ It's a pattern, really, and the `setTimeout()` itself is the iterator, not a `for` or `while` loop. – Jared Farrish Jun 09 '12 at 23:23
  • Personally, I like to use `setInterval` (vs. `setTimeout`) and a queue (vs. indexer), but... same ideas. –  Jun 09 '12 at 23:44
  • @pst - `setInterval()` can work, but for me the literalness of iteratively self-calling `setTimeout()` for these types of loops is the most direct and simple approach. This is probably one of my favorite patterns due to it's practicality and simplicity. – Jared Farrish Jun 09 '12 at 23:50

3 Answers3

12
// loop through array with all new ids
var i = 0, l = adArray.length;
(function iterator() {
    console.log(adArray[i]);

    if(++i<l) {
        setTimeout(iterator, 15000);
    }
})();

Something like this?

Cameron Martin
  • 5,952
  • 2
  • 40
  • 53
  • One, you never call the `countDown()`, so where/how is it run? And you really don't need the `for` loop; you can make the `setTimeout()` the iterator if you use scoping appropriately. For instance, wrap it all in a closure or function scope, including a nested function to call the `setTimeout()` and a `var loop` in the parent closure scope that's interated within the `setTimeout()`-containing function. – Jared Farrish Jun 09 '12 at 23:27
  • @ddlshack ah! this is what i am trying to accomplish, but there is one minor tweek. The console shows the length from the array how do i actually show the value within the array – Daniel Jun 09 '12 at 23:33
  • Check my update. It'll log the value of adArray. What do you want to be logged? – Cameron Martin Jun 09 '12 at 23:38
  • perfect that's just what i need. – Daniel Jun 09 '12 at 23:39
  • `arguments.callee` is deprecated in ECMAScript 5th edition. Instead the function name should be self-referenced: `(function process() { ...; setTimeout(process, ...); })`, for instance. (The function name scope is defined in ECMAScript 3rd edition.) –  Jun 09 '12 at 23:41
  • Interesting. Looking at https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope/arguments/callee it says using named functions "doesn't pollute the namespace". But surely it pollutes the global namespace? See under "ECMAScript 3 resolved these issues by allowing named function expressions" – Cameron Martin Jun 09 '12 at 23:48
  • Although in practice this is not necessarily going to be the case, note that the example "demonstrates" global variable pollution (in addition to the OPs code). As an example it's not a big deal, but note that in implementation the `var i = 0, l = adArray.length;` should not be interpreted as requiring to be global. Something along these lines: http://jsfiddle.net/userdude/JEzh4/ Which, although the compactness of this pattern is appealing, putting the function in a self-caller seems a little odd to me. But I can see how it could be elegant, depending on the approach required. – Jared Farrish Jun 10 '12 at 00:01
2

There's a really simple pattern for this type of iterator, using closure scope to store a loop counter and a nested looper() function which runs the setTimeout() iterator. The looper() function actually iterates the loop count, so there is no need for a for or do/while construct. I use this pattern often, and it works really well.

EDIT: Modified the condition to check for loop > 1, not loop > 0, which logged Loop count: 0. This can be tweaked, and technically, the looper() here runs 16 times.

(function(){
    var loop = 15;

    var looper = function(){
        console.log('Loop count: ' + loop);

        if (loop > 1) {
            loop--;
        } else {
            console.log('Loop end.');
            return;
        }

        setTimeout(looper, 15000);
    };

    looper();
})();

http://jsfiddle.net/userdude/NV7HU/2

Jared Farrish
  • 48,585
  • 17
  • 95
  • 104
  • You can just use arguments.callee to refer to the currently executing function. – Cameron Martin Jun 09 '12 at 23:37
  • And he wants a 15 second delay, not a 1 second delay. – Cameron Martin Jun 09 '12 at 23:39
  • Don't think he wants it to count from 1-15 either. He wants to iterate adArray. – Cameron Martin Jun 09 '12 at 23:41
  • Those are details. The pattern is easily tweaked, and I'd like to note your answer is a globalized version of what I suggested. It's the pattern that's important, so please stop sniping my answer if you do not have constructive criticism. `:)` – Jared Farrish Jun 09 '12 at 23:42
  • @ddlshack Left you a comment about `arguments.callee` and a better way to refer to the function :-) –  Jun 09 '12 at 23:43
  • @JaredFarrish I didn't mean to be confrontational. How does the scope of named functions work? – Cameron Martin Jun 09 '12 at 23:53
  • @ddlshack - Do you mean the scope of `looper()` above? – Jared Farrish Jun 10 '12 at 00:03
  • When using named functions, like in my updated code. `(function func() {})();` I thought any functions declared like that are assigned to the global scope, but it'd appear not. Are they just assigned to the local scope? – Cameron Martin Jun 10 '12 at 00:07
  • @ddlshack - It involves [closures](https://developer.mozilla.org/en/JavaScript/Guide/Closures), which took me a long time to "get it". Once you do, though, it just makes sense. If you form a closure or [function scope](https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope), scope "flows" down but not up, ie, you obtain privacy of context or scope. When you nest a function, it has a private function scope, but also [inherits the parent's scope](https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope#Nested_functions_and_closures). – Jared Farrish Jun 10 '12 at 00:30
  • @ddlshack - `(function bar(){})()` is a function expression in which `bar()` is only available within the scope of `bar()`, which is bizarre, right?. Kind've like the Javascript version of one hand clapping, if you will. However, it derives the "anonymous" privacy from being within the anonymous function scope or `()()`. Try this fiddle, see the error when trying to call `iterator()` out of scope: http://jsfiddle.net/userdude/JEzh4/1/ See how I can't call `iterator()` outside of it's anonymous function? That's really, really powerful. – Jared Farrish Jun 10 '12 at 00:30
  • @ddlshack - Oops, I had set the `i` in that last fiddle to `12` to test my `loop` concept. Check this out, though. This might melt your mind: http://jsfiddle.net/userdude/JEzh4/2/ – Jared Farrish Jun 10 '12 at 00:47
  • @ddlshack - That's a really interesting observation. I *think* what's going on is that the right-hand assignment is somehow a closure. Consider: http://jsfiddle.net/userdude/j5YDv/ and http://jsfiddle.net/userdude/j5YDv/2/ Do you want to move this to chat? I'd like to explore this a bit more, figure out what's going on. – Jared Farrish Jun 10 '12 at 01:31
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/12348/discussion-between-ddlshack-and-jared-farrish) – Cameron Martin Jun 10 '12 at 01:45
  • @ddlshack - I've at least determined it's a `FunctionExpression`, the question is how the variable interprets what it receives. I'm thinking it has something to do with the fact it's an *expression* and not a *declaration*, so it's contained within that assignment's "scope" (?). Or something. – Jared Farrish Jun 10 '12 at 01:46
1

Use this function to make it easier to run:

function loopArr(arr, callback, time, infinite){
    console.log('loop run');
    var i=0,
        total=arr.length-1;
    var loop=function(){
            // RUN CODE
            console.log('loop arr['+i+']');
            callback( arr[i] );
            if (i < total ) {
                i++;
            } else { // LOOP END
                console.log('loop end!');
                if(!infinite) return;
                i=0 //restart
            }
            setTimeout( loop, time);
    }
    loop()
}

To use this function execute this:

loopArr(arr, callback, time, infinite)

Where:

  • arr is the array we need to loop, it could be a jQuery selector
  • callback is the executed function with one argument returned which is the selected item
  • time is the timeout needed for delay
  • infinite is set true or false if we need the code to repeat itself forever

Example using animate.css :

var imgShowHide = function(elm){
    var elm = $(elm); // select the item arr[i] via jQuery
    elm.css('animation-duration','2s').show()
        .addClass('animated bounceInRight')
        .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
            elm.removeClass('animated bounceInRight')
                .addClass('animated bounceInLeft')
                .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
                    elm.removeClass('animated bounceInLeft').hide()
                })
        });
}

// RUN
loopArr( $('#images > img'), imgShowHide, 4000, true);
Ardit Hyka
  • 776
  • 2
  • 8
  • 18