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?
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 {
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'); }
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";
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);
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, }); }); });
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
//
});
});
}
}
-
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– Dan2014年01月03日 22:56:15 +00:00Commented Jan 3, 2014 at 22:56
-
\$\begingroup\$ Just wrote you an example. Let me know if something ain't clear. \$\endgroup\$Dan– Dan2014年01月03日 23:20:31 +00:00Commented Jan 3, 2014 at 23:20
1 Answer 1
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
.
-
\$\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– medoix2014年01月03日 23:49:28 +00:00Commented Jan 3, 2014 at 23:49
-
\$\begingroup\$ @medoix I've extracted some code in
getServiceBalance
but my code should be equivalent to yours (I'm also callingexec
and "gathering" results into a single array below by virtue ofQ.all
). See code belowgetServiceBalance
, on the very bottom. \$\endgroup\$Dan– Dan2014年01月04日 00:09:03 +00:00Commented Jan 4, 2014 at 0:09 -
\$\begingroup\$ @medoix Do you need to gather an array of results of calling
getreceivedbyaddress
for each item inaddress
and then put it intoservice
(myresult
)? \$\endgroup\$Dan– Dan2014年01月04日 00:10:59 +00:00Commented Jan 4, 2014 at 0:10 -
\$\begingroup\$ @medoix So
getAddressesByAccount
may return an"Offline"
string or an array? That's a bad design. \$\endgroup\$Dan– Dan2014年01月04日 00:12:34 +00:00Commented Jan 4, 2014 at 0:12 -
\$\begingroup\$ @medoix I fixed the code to call
getreceivedbyaddress
for each item inaddresses
(I think naming itaddress
is misleading if this is an array...) \$\endgroup\$Dan– Dan2014年01月04日 00:16:49 +00:00Commented Jan 4, 2014 at 0:16
Explore related questions
See similar questions with these tags.