Search code examples
javascriptnode.jspromisebluebird

Correct way to write loops for promise.


How to correctly construct a loop to make sure the following promise call and the chained logger.log(res) runs synchronously through iteration? (bluebird)

db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise

I tried the following way (method from http://blog.victorquinn.com/javascript-promise-while-loop )

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
});

var count = 0;
promiseWhile(function() {
    return count < 10;
}, function() {
    return new Promise(function(resolve, reject) {
        db.getUser(email)
          .then(function(res) { 
              logger.log(res); 
              count++;
              resolve();
          });
    }); 
}).then(function() {
    console.log('all done');
}); 

Although it seems to work, but I don't think it guarantees the order of calling logger.log(res);

Any suggestions?


Solution

  • I don't think it guarantees the order of calling logger.log(res);

    Actually, it does. That statement is executed before the resolve call.

    Any suggestions?

    Lots. The most important is your use of the create-promise-manually antipattern - just do only

    promiseWhile(…, function() {
        return db.getUser(email)
                 .then(function(res) { 
                     logger.log(res); 
                     count++;
                 });
    })…
    

    Second, that while function could be simplified a lot:

    var promiseWhile = Promise.method(function(condition, action) {
        if (!condition()) return;
        return action().then(promiseWhile.bind(null, condition, action));
    });
    

    Third, I would not use a while loop (with a closure variable) but a for loop:

    var promiseFor = Promise.method(function(condition, action, value) {
        if (!condition(value)) return value;
        return action(value).then(promiseFor.bind(null, condition, action));
    });
    
    promiseFor(function(count) {
        return count < 10;
    }, function(count) {
        return db.getUser(email)
                 .then(function(res) { 
                     logger.log(res); 
                     return ++count;
                 });
    }, 0).then(console.log.bind(console, 'all done'));