Chaining Asynchronous Functions Node.js bluebird mongoskin

554 views Asked by At

I have been reading many posts on how to chain asynchronous functions but I just can't seem to get it right!

As the title indicates. I am trying to chain mongoskin database calls together, so that i can gather all the information in chunks and then finally send the accumulated result in the response.

I have the object user as :

var User = {
       username: 'someusername',
       accounts: [{name: 'account_1'}, {name: 'account_2'}]
   }

For each of the accounts I need to gather data and then send the accumulated data in the response. So i am using the following for loop to iterate over the accounts:

var promise = require('bluebird');
var db = require('mongoskin').db('mongodb://localhost/someDB');

for(var x in user.accounts){
   //Fetch account data
   user.accounts[x].accountData = fetchAccountData(user.accounts[x].name);
}

//Finally send the collected response
response.send(user);

And the function fetchAccountData looks like the following:

function fetchAccountData(screen_id){
   db.collection('master')
     .aggregate([
        {$match: {screen_id: screen_id}}
      ], function(err, res){
           if(err)
              return null;
           else{
              console.log('Done', screen_id);
              return res;
          }
     });
 }

How can i chain this to have the following algorithm:

 start:
      for each account:
           fetchDataForAccount
      Finally:
           Send Response 
1

There are 1 answers

4
Travis Kaufman On BEST ANSWER

Your algorithm can be achieved using the following code:

var Promise = require('bluebird');
var mongo = require('mongoskin'), db;

Promise.promisifyAll(mongo.Collection.prototype);

db = mongo.db('mongodb://localhost/someDB');

Promise.all(user.accounts.map(function(acct) {
  return fetchAccountData(acct.name).then(function(data) {
    acct.accountData = data;
  });
}))
.then(function() {
  response.send(user);
})
.catch(function(err) {
  // handle error
});

function fetchAccountData(screen_id){
   return db
     .collection('master')
     .aggregateAsync([
        {$match: {screen_id: screen_id}}
      ]);
}

EDIT: Here's a breakdown of the code

The first thing you need to do is ensure that aggregate returns a Promise instead of using a continuation (e.g. callback). You can do this by using bluebird's amazing promisification abilities :) Here we use it on mongo.Collection.prototype so that when collection() is called it will return a promise-capable collection instance. Then we have fetchAccountData return the promise returned by aggregateAsync so the client has a way of knowing when that promise is resolved.

Next, we map over each account in accounts and return a promise which will be fulfilled once the account data is fetched and it has been assigned to the account object. We then use Promise.all which will return a promise that is fulfilled "when all the items in the array are fulfilled" (from the docs).

Finally, we have to use then() to "wait" until the promise returned from all has resolved, and the finally send back the response with the complete user object.