17

Do you know a JavaScript library that implements a generic Iterator class for collections (be it Arrays or some abstract Enumerable) with a full set of features, like the Google Common or the Apache Commons?

Edit: Enumerable#each is not an Iterator class. I'm looking for an Iterator, something that would let us write something like:

var iterator = new Iterator(myCollection);
for (var element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    // iterator 
}

Edit : mamoo reminded us of the Iterator implementation in Mozilla's Javascript 1.7. So the goal now is to find an implementation of this Iterator function in Javascript 1.5 (ECMA 4).

Edit2 : Why using an iterator when libraries (and ECMA 5) provide a each method? First, because each usually messes with this because the callback is call -ed (that's why each accepts a second argument in Prototype). Then, because people are much more familiar with the for(;;) construct than with the .each(callback) construct (at least, in my field). Lastly, because an iterator can iterate over plain objects (see JavaScript 1.7).

Edit3 : I accepted npup's anwser, but here is my shot at it :

function Iterator(o, keysOnly) {
    if (!(this instanceof arguments.callee))
      return new arguments.callee(o, keysOnly);
    var index = 0, keys = [];
    if (!o || typeof o != "object") return;
    if ('splice' in o && 'join' in o) {
        while(keys.length < o.length) keys.push(keys.length);
    } else {
        for (p in o) if (o.hasOwnProperty(p)) keys.push(p);
    }
    this.next = function next() {
        if (index < keys.length) {
            var key = keys[index++];
            return keysOnly ? key : [key, o[key]];
        } else throw { name: "StopIteration" };
    };
    this.hasNext = function hasNext() {
        return index < keys.length;
    };
}



var lang = { name: 'JavaScript', birthYear: 1995 };  
var it = Iterator(lang);
while (it.hasNext()) {
    alert(it.next());
}
//alert(it.next()); // A StopIteration exception is thrown  


var langs = ['JavaScript', 'Python', 'C++'];  
var it = Iterator(langs);
while (it.hasNext()) {
    alert(it.next());
}
//alert(it.next()); // A StopIteration exception is thrown  
3
  • Sure, that's why we waited 15 years to have it included in the language. Commented Apr 16, 2010 at 14:49
  • 2
    @stereofrog, many libraries and scripts nowadays use asynchronous function calls (well, until WebWorkers are generally supported, but even then) and at the moment, how do you suppose iterating through data asynchronously (in a non-blocking way) with a simple .each() function or a "for in" statement? Iterators are the best solution; they can be passed through asynchronous functions to resume iterations easily and regardless of underlaying implementation. (This is the iterator pattern after all). Javascript is not only a functional language... but even functional language do have iterators... Commented Dec 22, 2010 at 0:24
  • In edit 2, when you mentioned ES5's each method, did you mean the .forEach() method? Commented Jan 31, 2018 at 17:05

7 Answers 7

5

Ok, the enumerable pattern is not a real iterator then.

Is this (below) useful for you? It conforms to the sematics you gave at least. As usual there are tradeoffs to be made here and there, and I didn't think very hard when deciding this time :).
And maybe you would like to be able to send in a number or two and iterate over a range in that way. But this could maybe be a start (there's support for iterating over hashes, arrays and strings).

It's a whole demo page which runs itself and does some debug output, but the (possibly) interesting stuff is in the

window.npup = (function() {
    [...]
})();

spot.

Maybe it is just me who doesn't get it at all, but what would you use such a java-like Iterator for in a real situation?

Best /npup

<html>
<head>
<title>untitled</title>
</head>

<body>
    <ul id="output"></ul>


<script type="text/javascript">
window.log = (function (outputAreaId) {
    var myConsole = document.getElementById(outputAreaId);
    function createElem(color) {
        var elem = document.createElement('li');
        elem.style.color = color;
        return elem;
    }
    function appendElem(elem) {
        myConsole.appendChild(elem);
    }
    function debug(msg) {
        var elem = createElem('#888');
        elem.innerHTML = msg;
        appendElem(elem);
    }
    function error(msg) {
        var elem = createElem('#f88');
        elem.innerHTML = msg;
        appendElem(elem);
    }
    return {
        debug: debug
        , error: error
    };
})('output');


window.npup = (function () {
    // Array check as proposed by Mr. Crockford
    function isArray(candidate) {
        return candidate &&
            typeof candidate==='object' &&
            typeof candidate.length === 'number' &&
            typeof candidate.splice === 'function' &&
            !(candidate.propertyIsEnumerable('length'));
    }
    function dontIterate(collection) {
        // put some checks chere for stuff that isn't iterable (yet)
        return (!collection || typeof collection==='number' || typeof collection==='boolean');
    }
    function Iterator(collection) {
        if (typeof collection==='string') {collection = collection.split('');}
        if (dontIterate(collection)) {throw new Error('Oh you nasty man, I won\'t iterate over that ('+collection+')!');}
        var arr = isArray(collection);
        var idx = 0, top=0;
        var keys = [], prop;
        if (arr) {top = collection.length;}
        else {for (prop in collection) {keys.push(prop);}}
        this.next = function () {
            if (!this.hasNext()) {throw new Error('Oh you nasty man. I have no more elements.');}
            var elem = arr ? collection[idx] : {key:keys[idx], value:collection[keys[idx]]};
            ++idx;
            return elem;
        };
        this.hasNext = function () {return arr ? idx<=top : idx<=keys.length;};
    }
    return {Iterator: Iterator};
})();

var element;

log.debug('--- Hash demo');
var o = {foo:1, bar:2, baz:3, bork:4, hepp: {a:1,b:2,c:3}, bluff:666, bluff2:777};
var iterator = new npup.Iterator(o);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    log.debug('got elem from hash: '+element.key+' => '+element.value);
    if (typeof element.value==='object') {
        var i2 = new npup.Iterator(element.value);
        for (var e2=i2.next(); i2.hasNext(); e2=i2.next()) {
            log.debug('&nbsp;&nbsp;&nbsp;&nbsp;# from inner hash: '+e2.key+' => '+e2.value);
        }
    }
}
log.debug('--- Array demo');
var a = [1,2,3,42,666,777];
iterator = new npup.Iterator(a);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    log.debug('got elem from array: '+ element);
}
log.debug('--- String demo');
var s = 'First the pants, THEN the shoes!';
iterator = new npup.Iterator(s);
for (element = iterator.next(); iterator.hasNext(); element = iterator.next()) {
    log.debug('got elem from string: '+ element);
}
log.debug('--- Emptiness demo');
try {
    log.debug('Try to get next..');
    var boogie = iterator.next();
}
catch(e) {
    log.error('OW: '+e);
}

log.debug('--- Non iterables demo');
try{iterator = new npup.Iterator(true);} catch(e) {log.error('iterate over boolean: '+e);}
try{iterator = new npup.Iterator(6);} catch(e) {log.error('iterate over number: '+e);}
try{iterator = new npup.Iterator(null);} catch(e) {log.error('iterate over null: '+e);}
try{iterator = new npup.Iterator();} catch(e) {log.error('iterate over undefined: '+e);}

</script>
</body>
</html>
Sign up to request clarification or add additional context in comments.

2 Comments

Great job. I like how you iterated over strings, why not? For a justification of the question, I'm going to edit my question.
in Javascript, an iterator is useful if one needs to iterate through all the elements of an array, but in an asynchronous fashion (loop through the first n elements, then resume after a delay from the n+1 th element, etc.)
5

JQuery has the each() method: http://api.jquery.com/jQuery.each/

but probably there's something similar even in other libraries such as Moo or Dojo.

Javascript 1.7 implements the Iterator function: https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Iterators_and_Generators

1 Comment

Thanks for the js 1.7 link. I had forgot about that. I'd love to see an implementation of this in ECMA4.
3

This is my attempt (jsfiddle) for ECMAScript 262 5th edition (aka Javascript). (Uses for example Object.keys and Array.isArray)

//Usage
b=Iterator(a);
while(b()){
  console.log(b.value);
}

The code:

function Iterator(input,keys) {
  // Input:
  //  input : object|array
  //  keys   : array|undefined|boolean
  function my() {
    ++my.index;
    if (my.index >= my.keys.length) {
      my.index = my.keys.length -1;
      my.key = my.value = undefined;
      return false;
    }
    my.key = my.useIndex ? my.index : my.keys[my.index];
    my.value = my.input[my.key];
    return my.index < my.keys.length;
  }
  if (input === null || typeof input !== 'object') {
    throw new TypeError("'input' should be object|array");
  }
  if (
    !Array.isArray(keys)
    && (typeof keys !== 'undefined')
    && (typeof keys !== 'boolean')
    ) {
    throw new TypeError("'keys' should be array|boolean|undefined");
  }
  // Save a reference to the input object.
  my.input = input;
  if (Array.isArray(input)) {
    //If the input is an array, set 'useIndex' to true if 
    //the internal index should be used as a key.
    my.useIndex = !keys;
    //Either create and use a list of own properties,
    // or use the supplied keys
    // or at last resort use the input (since useIndex is true in that
    // case it is only used for the length)
    my.keys = keys===true ? Object.keys(input) : keys || input;
  } else {
    my.useIndex = false;
    my.keys = Array.isArray(keys) ? keys : Object.keys(input);
  }
  // Set index to before the first element.
  my.index = -1;
  return my;
}

Examples:

function Person(firstname, lastname, domain) {
  this.firstname = firstname;
  this.lastname = lastname;
  this.domain = domain;
}
Person.prototype.type = 'Brillant';

var list = [
  new Person('Paula','Bean','some.domain.name'),
  new Person('John','Doe','another.domain.name'),
  new Person('Johanna','Doe','yet.another.domain.name'),
];

var a,b; 
var data_array = ['A','B','C','D','E','F'];
data_array[10]="Sparse";


console.log('Iterate over own keys in an object, unknown order');
a = Iterator(list[0]);
while(a()) console.log("  ",a.key, a.value);

console.log('Iterate over keys from anywhere, in specified order');
a = Iterator(list[0], ['lastname','firstname','type']);
while(a()) console.log("  ",a.key, a.value);

console.log('Iterate over all values in an array');
a = Iterator(list);
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);


//Some abusing, that works for arrays (if the iterator.keys is modified
//it can also be used for objects)
console.log('Add more entries to the array, reusing the iterator...');
list.push(new Person('Another','Name','m.nu'));
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);

console.log('Reset index and print everything again...');
a.index=-1; //Reset the index.
while(a()) console.log(a.key, a.value.firstname, a.value.lastname);

//With arrays, if setting 'keys' to true it will only print the
//elements that has values (If the array has more own enumerable values
//they too will be included)
console.log('Print sparce arrays...');
a = Iterator(data_array,true);
while(a()) console.log(a.key, a.value);

Comments

2

In the time since this question was asked JavaScript has added actual Iterators. Some built-in types, such as Array, Map, and String now have a default iteration behavior, but you can add your own to any object by including a next() function which returns one of two objects:

{done:true}     /*or*/
{done:false, value:SOMEVALUE}

One way to access an object Iterator is with the:

for ( var of object ) { }

loop. Here is a (reasonably silly) example where we define an Iterator and then use it in such a loop to produce a string 1, 2, 3:

"use strict";

function count ( i ) {
  let n = 0;
  let I = {};
  I[Symbol.iterator] = function() {
     return { next: function() { return (n > i) ? {done:true}
                                                : {done:false, value:n++} } } };
  let s = "";
  let c = "";
  for ( let i of I ) {       /* use the iterator we defined above */
      s += c + i;
      c = ", "
  }
  return s;
}


let s = count(3);
console.log(s);

Comments

1

Ive used LINQ to Javascript in a few projects.

http://jslinq.codeplex.com/Wikipage

var myList = [
            {FirstName:"Chris",LastName:"Pearson"},
            {FirstName:"Kate",LastName:"Johnson"},
            {FirstName:"Josh",LastName:"Sutherland"},
            {FirstName:"John",LastName:"Ronald"},
            {FirstName:"Steve",LastName:"Pinkerton"}
            ];

var exampleArray = JSLINQ(myList)
                   .Where(function(item){ return item.FirstName == "Chris"; })
                   .OrderBy(function(item) { return item.FirstName; })
                   .Select(function(item){ return item.FirstName; });

2 Comments

LINQ looks great, but how is it relevant to Iterators? It looks like it's made for querying datasets.
LINQ is a functional programming library written in a DSL style which is why it looks like SQL.
1

I'm still a learner of js.class. Though being close to Ruby, helps me.

http://jsclass.jcoglan.com/enumerable.html

MarkT

Comments

1

Since this hasn't been mention yet arrays have higher-order functions built in.

Map works like iterator that can only do a single pass.

[1,2,3,4,5].map( function(input){ console.log(input); } );

This code passes each element in the list into a function, in this case its a simple printer.

1
2
3
4
5

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.