3
\$\begingroup\$

I'm attempting to create a final array with data pulled from 3 functions. The first two functions have all the data but then the 3rd function needs to run looping through one of the fields from function 2 in order to have the complete array at the end.

I'm looking for some advice and examples on how to fix this and or do it better. I have been advised promises would be a good solution, but could someone throw together a quick example so I can better get my head around it?

  1. In my services controller I have this as the page here and checks if the user is logged in.

    exports.all = function(req, res){
        if (!req.isAuthenticated()){
            res.redirect('login');
        } else {
    
  2. It uses the services database schema in the model to connect to the database and pull out the info I need:

    Database
        .find()
        .exec(function (err, services) {
          if (err) {
            return console.log(err);
          }
          if (!services) {
            console.log('Failed to load any Services');
          }
    
  3. Once the services are found (multiple RPC/API services that have the same commands but different data returned), I async through each and connect.

                async.mapSeries(services, function(service, cb) {
                    connect(service);
                    client.getBalance(req.user._id, 6, function(err, balance) {
                        console.log(balance);
                        if (err) balance = "Offline";
                        client.getAddressesByAccount(req.user._id, function(err, address) {
                            console.log(address);
                            if (err) address = "Offline";
    
  4. Once the first two lots of data is returned (the first is just a string number and the second is an array of addresses then for each address I want too async again and run a third command and then map the result of each to the corresponding address from above and output a final array to pass to the view.

                            if(address != "Offline") {
                                async.mapSeries(address, function (item, callback){ 
                                // print the key
                                client.cmd('getreceivedbyaddress', item, 6, function(err, addressrecevied) {
                                    if (err) console.log(err);
                                        console.log(item + " = " + addressrecevied);
                                        // console.log(JSON.stringify(addressrecevied, null, 4));
                                    });
                                callback(); // tell async that the iterator has completed
                                }, function(err) {
                                    console.log('iterating done');
                                });
                            };
    
    
    
                            // console.log(service.abr + " address = " + address);
                            console.log(service.abr + " balance = " + balance);
    
  5. Callback that outputs the current top two results to the view variables:

                            return cb(null, {
                                    name: service.name,
                                    display: service.display,
                                    address: address,
                                    balance: balance,
                                    abr: service.abr,
                            });
                        });
                    });
    
  6. Function to call the view and pass variables when done:

                }, function(err, services) {
                        // console.log(services);
                        if (err) {
                                return console.log(err);
                        }
    
                    req.breadcrumbs('Services', '/services/all');
                        res.render('services/index', {
                                title: 'Services',
                                icon: 'iconfa-cog',
                                summary: 'Services information',
                                services: services,
                                path : req.path,
                                breadcrumbs: req.breadcrumbs(),
                                udata : req.session.user
                        });
                    });
            });
        }
    }
    

Work in progress

exports.all = function(req, res){
if (!req.isAuthenticated()){
    res.redirect('login');
} else {
    Wallet
    .find()
    .exec(function (err, wallets) {
      if (err) {
        return console.log(err);
      }
      if (!wallets) {
        console.log('Failed to load any Wallets');
      }
      async.mapSeries(wallets, function(coin, cb) {

                //
                // Q FUNCTION START
                //

                function getServiceBalance(service) {
                  connect(service);
                  cmd = Q.denodeify(client.cmd.bind(client)),
                getBalance = Q.denodeify(client.getBalance.bind(client)),
                getAddressesByAccount = Q.denodeify(client.getAddressesByAccount.bind(client));

                  client.getBalance(req.user._id, 6, function(err, balance) {
                        console.log("Service: " + service.name + " - Balance: " + balance);
                        if (err) balance = "Offline";
                    });

                  // We're going to fill this in step by step...
                  var result = {
                    name: service.name,
                    display: service.display,
                    abr: service.abr
                  };

                  return getBalance(req.user._id, 6)
                    .then(function (balance) {
                      result.balance = balance;
                      console.log("Balance:" + balance);
                      console.log("User ID: " + req.user._id);
                      return getAddressesByAccount(req.user._id);
                    })
                    .then(function (addresses) {
                      result.address = addresses;
                      console.log("Addresses:" + result.address);

                      if (!_.isArray(addresses)) {
                        // Throwing errors inside `then` is fine: they will get caught in `catch` below
                        throw new Error('Could not get addresses array');
                      }

                      // For each address, call getreceivedbyaddress and gather results in array
                      return Q.all(_.map(addresses, function (address) {
                        return cmd('getreceivedbyaddress', address, 6);
                      }));
                    }).then(function (addressesReceived) {
                      result.addressReceived = addressesReceived;
                    }).catch(function (err) {
                      // Here's the catch method--the *one and only* place all errors propagate to.
                      console.log(err);

                      result.balance = 'Offline';
                      result.address = 'Offline';
                      result.addressReceived = 'Offline';
                    }).thenResolve(result);
                }


                exec().then(function (services) {
                  if (!services) {
                    throw new Error('Failed to load any Services');
                  }

                  return Q.all(_.map(services, getServiceBalance), function (serviceBalances) {
                    req.breadcrumbs('Services', '/services/all');
                      res.render('services/index', {
                        title: 'Services',
                        icon: 'iconfa-cog',
                        summary: 'Services information',
                        services: serviceBalances,
                        path : req.path,
                        breadcrumbs: req.breadcrumbs(),
                        udata : req.session.user
                      });
                    });
                  }).done();

                //
                // Q FUNCTION END
                //

            });
        });
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Have you considered ditching async/callbacks for promises, e.g. with Q? Edit: Sorry, I haven't noticed you already said that. \$\endgroup\$ – Dan Jan 3 '14 at 22:56
  • \$\begingroup\$ Just wrote you an example. Let me know if something ain't clear. \$\endgroup\$ – Dan Jan 3 '14 at 23:20
2
\$\begingroup\$

With Q promises it would look more or less like this (taking this chat into account):

var mongoose = require('mongoose') 
, Q = require('q')
, Wallet = mongoose.model('Wallet')
, _ = require('underscore')
, bitcoin = require('bitcoin');

function connect(coin) { 
  return new bitcoin.Client({ 
    host: coin.host, 
    port: coin.port, 
    user: coin.user, 
    pass: coin.pass, 
  }); 
}

var wallet = Wallet.find(),
    exec = Q.denodeify(wallet.exec.bind(wallet));

function getServiceBalance(service, userID) {
  var client = connect(service)
  , cmd = Q.denodeify(client.cmd.bind(client))
  , getBalance = Q.denodeify(client.getBalance.bind(client))
  , getAddressesByAccount = Q.denodeify(client.getAddressesByAccount.bind(client));

  // We're going to fill this in step by step...
  var result = {
    name: service.name,
    display: service.display,
    abr: service.abr
  };

  return getBalance(userID, 6)
    .then(function (balance) {
      result.balance = balance;
      console.log("Balance:" + balance);
      console.log("User ID: " + userID);
      return getAddressesByAccount(userID);
    })
    .then(function (addresses) {
      console.log("Addresses:", address);

      if (!_.isArray(addresses)) {
        // Throwing errors inside `then` is fine: they will get caught in `catch` below
        throw new Error('Could not get addresses array');
      }

      // For each address, call getreceivedbyaddress and gather results in array
      return Q.all(_.map(addresses, function (address) {
        return cmd('getreceivedbyaddress', address, 6).then(function (received) {
          return [address, received];
        });
      }));
    }).then(function (addressesReceived) {
      result.address = _.object(addressesReceived);
    }).catch(function (err) {
      // Here's the catch method--the *one and only* place all errors propagate to.
      console.log(err);

      result.balance = 'Offline';
      result.address = 'Offline';
    }).thenResolve(result);
}

exports.all = function (req, res) {
  if (!req.isAuthenticated()){
      res.redirect('login');
  } else {
    exec().then(function (services) {
      if (!services) {
        throw new Error('Failed to load any Services');
      }

      var promises = _.map(services, function (service) {
          return getServiceBalance(service, req.user._id);
      });

      return Q.all(promises);
    }).then(function (serviceBalances) {
      req.breadcrumbs('Services', '/services/all');
      res.render('services/index', {
        title: 'Services',
        icon: 'iconfa-cog',
        summary: 'Services information',
        services: serviceBalances,
        path : req.path,
        breadcrumbs: req.breadcrumbs(),
        udata : req.session.user
      });
    }).catch(function (err) {
      console.log('Error:', err);
      // TODO: render an error message?
    }).done();
  }
}

Note that we could've gone side-effect-free and remove captured result variable from getServiceBalance but I don't think it's worth the hassle, and in fact debugging with state is more convenient, as long as the state is properly isolated.

If you're looking to Q-style alternative to mapSeries, check out Q.all and Q.allSettled.

\$\endgroup\$
  • \$\begingroup\$ Thanks Dan! I take it this is just a function and i will still need the top part of my page function to find the services and use async to go through each and run this function you have created? the getreceivedbyaddress in the code os the third function i could not get working and basically i need to run that command for each address returned in the previous function (returned in an array) and then add the result as a sub item in the array to the address above. \$\endgroup\$ – medoix Jan 3 '14 at 23:49
  • \$\begingroup\$ @medoix I've extracted some code in getServiceBalance but my code should be equivalent to yours (I'm also calling exec and “gathering” results into a single array below by virtue of Q.all). See code below getServiceBalance, on the very bottom. \$\endgroup\$ – Dan Jan 4 '14 at 0:09
  • \$\begingroup\$ @medoix Do you need to gather an array of results of calling getreceivedbyaddress for each item in address and then put it into service (my result)? \$\endgroup\$ – Dan Jan 4 '14 at 0:10
  • \$\begingroup\$ @medoix So getAddressesByAccount may return an "Offline" string or an array? That's a bad design. \$\endgroup\$ – Dan Jan 4 '14 at 0:12
  • \$\begingroup\$ @medoix I fixed the code to call getreceivedbyaddress for each item in addresses (I think naming it address is misleading if this is an array...) \$\endgroup\$ – Dan Jan 4 '14 at 0:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.