stewe
stewe

Reputation: 42644

Iterate over object keys in node.js

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.

Upvotes: 159

Views: 340301

Answers (6)

Gershom Maes
Gershom Maes

Reputation: 8170

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!

Upvotes: 0

H6_
H6_

Reputation: 32847

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

Upvotes: 5

amateur barista
amateur barista

Reputation: 4530

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);

Upvotes: 26

Raynos
Raynos

Reputation: 169451

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);
});

Upvotes: 268

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");

Upvotes: 3

Lindy
Lindy

Reputation: 297

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);
    });

Upvotes: 0

Related Questions