node.js deferred misunderstanding

I`m learning q.js and trying to query 3 collections simultaneously with its help (avoiding callback hell):

var Q = require('q')
var deferred = Q.defer();

users() is a wrapper of db.collection.find()

var users = function (){
    Auth.listUsers({role:'user'}, call)
    return deferred.promise
}

call() is a shorthand for exporting promises

var call = function (err,data){
    if (err) {
        deferred.reject(err);
    } else {
        deferred.resolve(data);
    }
}

loop() is main loop, which gets cursor and loops through entries

var loop = function (result) {
    var list = []
    var promises = [];
    result.each(function (err,data){
        if (err) {
            deferred.reject(err);
        } else {
            deferred.resolve(data);
            promises.push(deferred.promise)
            console.log('promises_:', promises) // <- internal
        }
    })
    console.log('promises:', promises) // <- external
    return Q.all(promises)
}

code:

users()
  .then(loop)
  .then(function(ful){
            console.log('ful:', ful);  // <- here should come the queries to other collections
        })

result of console logging at the end:

promises: []    //external is empty
ful: []
promises_: [ [object Object] ]  // internal is being filled
promises_: [ [object Object], [object Object] ]

As you can see, the callback of .each is executed later than pushing promises to array. I believe it can be made by using result.toArray() instead of .each, but how can it be done with the help of .each loop?

result is a cursor, returned by mongodb driver after db.collection.find() call (http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#find).

Answers


var deferred = Q.defer();
…
deferred.resolve(…);
…
deferred.resolve(…);

call() is a shorthand for exporting promises

That will not work! You need to create a new deferred for each promise that you want. However, you shouldn't use Deferreds anyway! Instead, use the many Node callback helper functions.


As you can see, the callback of .each is executed later than pushing promises to array. I believe it can be made by using result.toArray() instead of .each, but how can it be done with the help of .each loop?

It cannot, unless you know beforehand how often each will be called and how many promises will need to be created. Q.all is a bit useless in here, since the promises are not created at once and execute their tasks in parallel - instead, they are a stream.

You really should use toArray here, to get a single callback with which you resolve the promise.

Well, there is a way, but it's ugly and less efficient than toArray. You can have one deferred that will always wait, and is only resolved with a promise for the rest of the stream.

function loop(result) {
    var deferred = Q.defer();
    result.each(function (err,data){
        if (err) {
            deferred.reject(err);
        } else if (data == null) {
            deferred.resolve([]); // end of the stream
        } else {
            var nextDeferred = Q.defer();
            deferred.resolve(nextDeferred.promise.then(function(rest) {
                return [data].concat(rest);
            }));
            deferred = nextDeferred;
        }
    })
    return deferred.promise;
}

Need Your Help

Why does LinkedHashSet<E> extend HashSet<e> and implement Set<E>

java collections hashtable

Opened a LinkedHashSet source code today and found some interesting thing:

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.