157

Since Javascript 1.7 there is an Iterator object, which allows this:

var a={a:1,b:2,c:3};
var it=Iterator(a);

function iterate(){
    try {  
        console.log(it.next());
        setTimeout(iterate,1000);
    }catch (err if err instanceof StopIteration) {  
        console.log("End of record.\n");  
    } catch (err) {  
        console.log("Unknown error: " + err.description + "\n");  
    }  

}
iterate();

is there something like this in node.js ?

Right now i'm using:

function Iterator(o){
    /*var k=[];
    for(var i in o){
        k.push(i);
    }*/
    var k=Object.keys(o);
    return {
        next:function(){
            return k.shift();
        }
    };
}

but that produces a lot of overhead by storing all the object keys in k.

stewe
  • 41,820
  • 13
  • 79
  • 75
  • Have you seen this? http://ejohn.org/blog/unimpressed-by-nodeiterator/ – jcolebrand Sep 16 '11 at 04:57
  • 2
    What overhead ? How many keys and iterators you have ? If their product is less than 1 million, just ignore this 'inefficiency'. – c69 Sep 16 '11 at 05:33
  • @jcolebrand φ: It seems `createNodeIterator` is for DOM elements, i don't even have a DOM ;) @c69: i store all data in the `keys` of the object and the `value` is just set to `1` (about 20MB in 700k keys), indeed, for now i'm just ignoring this 'overhead', but i would prefer a better solution :) – stewe Sep 16 '11 at 07:10
  • I saw it as a class to be messed with ;-) – jcolebrand Sep 16 '11 at 14:35

6 Answers6

265

What you want is lazy iteration over an object or array. This is not possible in ES5 (thus not possible in node.js). We will get this eventually.

The only solution is finding a node module that extends V8 to implement iterators (and probably generators). I couldn't find any implementation. You can look at the spidermonkey source code and try writing it in C++ as a V8 extension.

You could try the following, however it will also load all the keys into memory

Object.keys(o).forEach(function(key) {
  var val = o[key];
  logic();
});

However since Object.keys is a native method it may allow for better optimisation.

Benchmark

As you can see Object.keys is significantly faster. Whether the actual memory storage is more optimum is a different matter.

var async = {};
async.forEach = function(o, cb) {
  var counter = 0,
    keys = Object.keys(o),
    len = keys.length;
  var next = function() {
    if (counter < len) cb(o[keys[counter++]], next);
  };
  next();
};

async.forEach(obj, function(val, next) {
  // do things
  setTimeout(next, 100);
});
thepeer
  • 8,190
  • 2
  • 18
  • 13
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • Thanks !, this improves my iterator a bit :) (updated the code) but sadly the memory issue remains :( And i can not use `forEach` since each iteration step should be invoked from an async `setTimeout`. – stewe Sep 16 '11 at 08:41
  • @stewe added an `async.forEach` – Raynos Sep 16 '11 at 08:49
  • 2
    @stewe if you manage to write it, publish it on github and leave a link to it in an answer here or a comment o/ – Raynos Sep 16 '11 at 22:20
  • @stewe about that C++ extension, did you author it? – Raynos Jan 23 '12 at 18:12
  • For React, I had to use .map() instead of .forEach() even though it is an object. See http://stackoverflow.com/questions/29534224/react-jsx-iterating-through-a-hash-and-returning-jsx-elements-for-each-key – James Gentes Nov 15 '16 at 20:07
  • That benchmark link is broken. It should be updated or edited out, but then a part of the answer is missing/without source. – CloudWatcher Jul 20 '23 at 15:55
26

Also remember that you can pass a second argument to the .forEach() function specifying the object to use as the this keyword.

// myOjbect is the object you want to iterate.
// Notice the second argument (secondArg) we passed to .forEach.
Object.keys(myObject).forEach(function(element, key, _array) {
  // element is the name of the key.
  // key is just a numerical value for the array
  // _array is the array of all the keys

  // this keyword = secondArg
  this.foo;
  this.bar();
}, secondArg);
Luke
  • 477
  • 4
  • 9
amateur barista
  • 4,440
  • 3
  • 25
  • 37
  • 8
    nice addition to the thread, but ... why on earth show the key of the object being passed as something called "element", and the enumerator for the keys array called "key" ?! Can I suggest you update your code sample to use `Object.keys(myObject).forEach(function(key, index, arrayOfKeys) {` – Andy Lorenz Mar 04 '19 at 17:03
4

For simple iteration of key/values, sometimes libraries like underscorejs can be your friend.

const _ = require('underscore');

_.each(a, function (value, key) {
    // handle
});

just for reference

disco crazy
  • 31,313
  • 12
  • 80
  • 83
3

I'm new to node.js (about 2 weeks), but I've just created a module that recursively reports to the console the contents of an object. It will list all or search for a specific item and then drill down by a given depth if need be.

Perhaps you can customize this to fit your needs. Keep It Simple! Why complicate?...

'use strict';

//console.log("START: AFutils");

// Recusive console output report of an Object
// Use this as AFutils.reportObject(req, "", 1, 3); // To list all items in req object by 3 levels
// Use this as AFutils.reportObject(req, "headers", 1, 10); // To find "headers" item and then list by 10 levels
// yes, I'm OLD School!  I like to see the scope start AND end!!!  :-P
exports.reportObject = function(obj, key, level, deep) 
{
    if (!obj)
    { 
        return;
    }

    var nextLevel = level + 1;

    var keys, typer, prop;
    if(key != "")
    {   // requested field
        keys = key.split(']').join('').split('[');
    }
    else
    {   // do for all
        keys = Object.keys(obj);
    }
    var len = keys.length;
    var add = "";
    for(var j = 1; j < level; j++)
    {
        // I would normally do {add = add.substr(0, level)} of a precreated multi-tab [add] string here, but Sublime keeps replacing with spaces, even with the ["translate_tabs_to_spaces": false] setting!!! (angry)
        add += "\t";
    }

    for (var i = 0; i < len; i++) 
    {
        prop = obj[keys[i]];
        if(!prop)
        {
            // Don't show / waste of space in console window...
            //console.log(add + level + ": UNDEFINED [" + keys[i] + "]");
        }
        else
        {
            typer = typeof(prop);
            if(typer == "function")
            {
                // Don't bother showing fundtion code...
                console.log(add + level + ": [" + keys[i] + "] = {" + typer + "}");
            }
            else
            if(typer == "object")
            {
                console.log(add + level + ": [" + keys[i] + "] = {" + typer + "}");
                if(nextLevel <= deep)
                {
                    // drop the key search mechanism if first level item has been found...
                    this.reportObject(prop, "", nextLevel, deep); // Recurse into
                }
            }
            else
            {
                // Basic report
                console.log(add + level + ": [" + keys[i] + "] = {" + typer + "} = " + prop + ".");
            }
        }
    }
    return ;
};

//console.log("END: AFutils");
MarmiK
  • 5,639
  • 6
  • 40
  • 49
0

adjust his code:

Object.prototype.each = function(iterateFunc) {
        var counter = 0,
keys = Object.keys(this),
currentKey,
len = keys.length;
        var that = this;
        var next = function() {

            if (counter < len) {
                currentKey = keys[counter++];
                iterateFunc(currentKey, that[currentKey]);

                next();
            } else {
                that = counter = keys = currentKey = len = next = undefined;
            }
        };
        next();
    };

    ({ property1: 'sdsfs', property2: 'chat' }).each(function(key, val) {
        // do things
        console.log(key);
    });
Lindy
  • 287
  • 5
  • 13
0

You can easily write your own iterator:

let iterateObject = function*(obj) {
  for (let k in obj) yield [ k, obj[k] ];
};

Now we can do:

let myObj = {
  a: 1,
  b: 2,
  c: 3
};
for (let [ k, v ] of iterateObject(myObj)) {
  console.log({ k, v });
}

// Produces:
// >> { k: 'a', v: 1 }
// >> { k: 'b', v: 2 }
// >> { k: 'c', v: 3 }

Note that this approach successfully avoids storing all Object values in memory!

It's also possible to modify the prototype (which is often disliked, but I find if you have sufficient control of your environment, and use Object.defineProperty with { enumerable: false }, there's little-to-no downside):

// Add a Symbol.iterator property to Object.prototype
Object.defineProperty(Object.prototype, Symbol.iterator, {
  enumerable: false,
  value: function*() {
    for (let k in this) yield [ k, this[k] ];
  }
});

// Now we can iterate through any plain object
let obj = { a: 1, b: 2, c: 3, d: 4, e: 5 };
for (let [ k, v ] of obj) console.log([ k, v ]);

Note that function*(){} is supported since node version 4.0.0, and destructuring (as seen in the for loop which consumes the generator) is supported since 6.0.0!

Gershom Maes
  • 7,358
  • 2
  • 35
  • 55