Abel
Abel

Reputation: 355

Mongodb iterate a collection of in a synchronous way using promises

I have a method (see code example) and I'm trying to return a promises list to parent method. My problem is when pushing promises in my promiseList array.

getDistance is a method returning a Promise. If I do .then() in getDistance method,everything is OK. Nevertheless, if I try to push the promise into my array, when I do .then in parent method, it is empty.

I think it may happen due to each loop is asynchronous...

var promiseList = [];
    res = db.collection.find query statement... 
    res.each(function(err, doc) {
        if (doc!==null) {
            promiseList.push(
                 getDistance(orgLat, orgLon, destLat, desLon)
            );
        }
    });
    return Promise.all(promTimeList);

I'm using MongoDB as database, and NodeJS in server side, with mongodb as driver to connecto to my database and Bluebird as library to implement promises.

Thanks in advance!

Upvotes: 1

Views: 1302

Answers (2)

Basil Musa
Basil Musa

Reputation: 8718

Iterate the collection syncrhonously by using await with cursor.next() in a while loop:

var cursor = db.collection.find({});

while ((doc = await cursor.next()) != null) {
        promiseList.push(
             getDistance(doc.orgLat, doc.orgLon, doc.destLat, doc.desLon);
        );
}

Upvotes: 1

markwatsonatx
markwatsonatx

Reputation: 3501

I think you are correct in that the issue is due to the fact that each is asynchronous. You can use defer to wrap callback APIs (like each) into promises. It would work something like this:

res = db.collection.find query statement... 
processResponse(res)
    .then(function(promiseList) {
        // execute all the promises
        return Promise.all(promiseList);
    }, function (err) {
        // handle error
    });    

function processResponse(res) {
    var deferred = Promise.pending();
    var promiseList = [];
    res.each(function(err, doc) {
        if (err) {
            // error, abort
            deferred.reject(err);
            return;
        }
        else if (doc != null) {
            promiseList.push(
                getDistance(orgLat, orgLon, destLat, desLon)
            );
        }
        else {
           // finished looping through all results, return the promise list
           deferred.resolve(promiseList);
        }
    });
    return deferred.promise;
}

See more on defer with bluebird at the following link (look for "So when should deferred be used?"):

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

Update: According to this post:

Define empty Bluebird promise like in Q

It looks like there is a Promise constructor that may be the preferred way to do this. The processResponse method would look like this:

function processResponse(res) {
    return new Promise(function(resolve, reject) {
        var promiseList = [];
        res.each(function(err, doc) {
            if (err) {
                // error, abort
                reject(err);
                return;
            }
            else if (doc != null) {
                promiseList.push(
                    getDistance(orgLat, orgLon, destLat, desLon)
                );
            }
            else {
               // finished looping through all results, return the promise list
               resolve(promiseList);
            }
        });
    });
}

Thanks to @Bergi. Please correct me if I am wrong. I am more familiar with the Q library (https://github.com/kriskowal/q) than bluebird.

Upvotes: 1

Related Questions