kYuZz
kYuZz

Reputation: 1732

Iterating with Q

I have this collection in MongoDB (I'm omitting _ids for brevity):

test> db.entries.find();
{
  "val": 1
}
{
  "val": 2
}
{
  "val": 3
}
{
  "val": 4
}
{
  "val": 5
}

I need to perform some processing on each document which I cannot do with db.update(). So, in a nutshell, what I need to do is retrieving one document at a time, process it in Node and save it back to Mongo.

I'm using the Monk library, and Q for promises. Here's what I've done — I didn't include the processing/save bit for brevity:

var q = require('q');

var db = require('monk')('localhost/test');
var entries = db.get('entries');

var i = 1;
var total;

var f = function (entry) {
    console.log('i = ' + i);
    console.log(entry.val);
    i++;
    if (i <= total) {
        var promise = entries.findOne({ val: i });
        loop.then(function (p) {
            return f(p);
        });
        return promise;
    }
};

var loop = q.fcall(function () {

    return entries.count({});

}).then(function (r) {

    total = r;
    return entries.findOne({ val: i });

}).then(f);

I would expect this code to print out:

i = 1
1
i = 2
2
i = 3
3
i = 4
4
i = 5
5

but it actually prints out:

i = 1
1
i = 2
2
i = 3
2
i = 4
2
i = 5
2

What am I doing wrong?

Upvotes: 0

Views: 50

Answers (1)

Quentin Roy
Quentin Roy

Reputation: 7887

In your code, loop is one and only one promise. It is executed only once. It is not a function. Inside f, loop.then(f) just trigger f with the result of the promise (it has already been executed so it is not executed again).

You actually want to create several promises. What you are looking for is something that should looks like:

var q = require('q');

var db = require('monk')('localhost/test');
var entries = db.get('entries');

var i = 1;
var total;

var f = function (entry) {
    console.log('i = ' + i);
    console.log(entry.val);
    i++;
    if (i <= total) {
        // I am not sure why you put entries.findOne here (looks like a mistake, 
        // its returned value isn't used) but if you really need it to be done
        // before loop, then you must pipe it before loop
        return entries.findOne({ val: i }).then(loop);
        // do not pipe f again here, it is already appended at the end of loop
    }
};

function loop(){
    return q.fcall(function () {
        return entries.count({});
    }).then(function (r) {
        total = r;
        return entries.findOne({ val: i });
    }).then(f);
}

loop();

If you are interested, here is a very nice article about promises.

Upvotes: 1

Related Questions