I am creating my first node app using ExpressJS and I am confused about how to requesting multiple url's using Request and async. The code I have below spits out the following error, "chart1 is not defined".
router.get('/', function(req, res) {
async.parallel([
function(next) {
apiRequestGoesHere(chart1, function(error) {
request.get('http://203.33.33.44:8080/reports/43?type=0&key=fakekey'),
next(null, firstData);
});
},
function(next) {
anotherApiRequest(chart2, function(error) {
request.get('http://203.33.33.44:8080/reports/42?type=0&key=fakekey'),
next(null, secondData);
});
}],
function(err, results, firstData, secondData) {
// results is [firstData, secondData]
res.render('index', {
title: 'Home KP',
firstData: chart1
});
});
});
Can someone please explain to me how to define these and get this working?
Your callback is not defining chart1 anywhere. Maybe you want to check results[0]?
With async.parallel, the results variable contains the results of all the functions, in the same order. When you call next(null, firstData), you're telling async to put the value of firstData into the results array, and since that function is the first, it will go into results[0] (next one is results[1], etc).
Related
I am trying to scan the top 100 movie torrents on the pirate bay using node and add a movie poster for each result.
I am using these libraries
thepiratebay
imdb-api
I am able to find top 100 and return the results with no problems
app.get('/movies', function(req, res){
tpb.topTorrents(207).then(function(topMovies){
async.map(topMovies, tpb.getTorrent, function(err, results){
res.send(results);
})
})
});
I am also able to look up movies via an IMDB ID and return the results
app.get('/imdb', function(req, res){
imdb.getReq({ id: 'tt2660888' }, function(err, things) {
res.send(things);
});
});
What I am trying to do is loop over the top 100 results pull the imdb id out of the description field out and query imdb replacing the picture field with result.
app.get('/movies', function(req, res){
tpb.topTorrents(207).then(function(topMovies){
async.map(topMovies, tpb.getTorrent, function(err, results){
for (var value of results) {
if (S(value.description).contains('www.imdb.com/title/')) {
var imdbId = S(value.description).between('www.imdb.com/title/', '/').s
imdb.getReq({ id: imdbId }, function(err, movie) {
value["picture"] = movie.poster
});
}
}
res.send(results);
})
})
});
This isn't working for some reason but it makes sense to me intuitively. If I remove the imdb-api call and replace it with value["picture"] = "foo". It does work. I'm not sure if this is related to how node handles loops. I'm new to the JS world and have a ruby background
Thanks in advance
You are on the right track with the async module but the imdb requests are also asynchronous so res.send just gets called with the initial result of async.map
You can use another async.map for the imdb calls and them chain them with async.waterfall which will pass the results of the first function as an argument to the second (async.apply just invokes the tpb function with your topMovies).
function tpb (topMovies, done) {
async.map(topMovies, tpb.getTorrent, done);
}
function imdb (movies, done) {
function lookup (value, callback) {
if (S(value.description).contains('www.imdb.com/title/')) {
var imdbId = S(value.description).between('www.imdb.com/title/', '/').s
imdb.getReq({ id: imdbId }, function(err, movie) {
value["picture"] = movie.poster
return cb(err, value);
});
} else {
return callback(null);
}
}
async.map(movies, lookup, done);
}
app.get('/movies', function(req, res){
tpb.topTorrents(207).then(function(topMovies){
async.waterfall([async.apply(tpb, topMovies), imdb], function (err, results) {
if (err) {
// do error handling
}
return res.send(results);
});
});
});
I always have multiple operations in one route or endpoint. Take an example below, when a user deletes an item, I want the related file be deleted in s3 too besides deleting related collection from the database.
So is the code below ok? Does it matter if I put the first function (delete file from s3) inside the DeleteItem function?
router.post('/item/delete', function(req, res) {
if(req.body.dlt_item){
var tempArray = [];
tempArray.push({"Key":req.body.dlt_item});
s3Bucket.deleteObjects({
Bucket: 'myS3',
Delete: {
Objects: req.body.dlt_item
}
}, function(err, data) {
if (err)
return console.log(err);
});
}
Item.DeleteItem(req.body.item_id, function(err,result){
if(err){console.log(err)}
res.send({result:1});
})
});
You should organise your code like this. This will ensure that s3 deletion will start only when mongodb deletion has finished.
In your code both things happen simultaneously. this may cause issue in some cases.
If one fails and other succeeds then there will be trouble. Suppose s3 files get deleted successfully and mongo deletion fails. Then you will have many references to non existing resources.
router.post('/item/delete', function(req, res) {
if(req.body.dlt_item){
var tempArray = [];
tempArray.push({"Key":req.body.dlt_item});
Item.DeleteItem(req.body.item_id, function(err,result){
if(err)
{
console.log(err)
res.send(err);
}
else
{
//deletion from mongodb is succesful now delete from s3
s3Bucket.deleteObjects({
Bucket: 'myS3',
Delete: {
Objects: req.body.dlt_item
}
},function(err, data) {
if (err)
{
// deletion from s3 failed you should handle this case
res.send({result:1});
return console.log(err);
}
else
{
// successful deletion from both s3 and mongo.
// If you do not want to wait for this then send the response before this function.
res.send({result:1});
}
});
}
})
});
so i have this problem i am working on 'following' feature in my application. What's important, i have two models:
Follows and Notifications
When I hit follow button in front-end I run function from follow.client.controller.js which POSTs to API endpoint /api/follows which corresponds to follow.server.controller.js and then update action on Follows model is performed - easy. AFAIK thats how it works (and it works for me).
But in follows.server.controller.js I want also invoke post to API endpoint at /api/notifications which corresponds to notifications.server.controller.js but I can't find a proper way to do that. Any help will be appreciated.
I don't want another call from front-end to add notification because it should be automatic = if user starts following someone, information is saved in both models at once.
You can add middleware in your server route.
app.route('/api/follows')
.post(notification.firstFunction, follows.secondFunction);
And now add 2 methods in your contollers. First makes the call to db and add's some result's data to request object which will be forwarded to second method.
exports.firstFunction= function(req, res, next) {
Notification.doSometing({
}).exec(function(err, result) {
if (err) return next(err);
req.yourValueToPassForward = result
next(); // <-- important
});
};
exports.secondFunction= function(req, res) {
//...
};
Or you can make few database calls in one api method, joining this calls with promises. Example:
var promise = Meetups.find({ tags: 'javascript' }).select('_id').exec();
promise.then(function (meetups) {
var ids = meetups.map(function (m) {
return m._id;
});
return People.find({ meetups: { $in: ids }).exec();
}).then(function (people) {
if (people.length < 10000) {
throw new Error('Too few people!!!');
} else {
throw new Error('Still need more people!!!');
}
}).then(null, function (err) {
assert.ok(err instanceof Error);
});
I'm learning node.js so bear with me.
I'm trying to create a node.js web application using express+jade that is basically just a line queue. (I.E. take a number, wait in line, now serving number 4...except the 4 will be a mysql table field). The page will auto-update every 5 seconds. There are three line queues handled by the page (I.E) :3000/1 :3000/2 :3000/3.
To be clear, I have the application working, but I want to make sure I am doing it correctly as opposed to just hacking it together with poor methodology.
In my index.js I have the standard setup:
exports.bio = function(req, res){
res.render('index', {
location: 'Biometrics',
number: bio()
});
};
exports.interview = function(req, res){
res.render('index', {
location: 'Interview',
number: interview()
});
};
exports.docs = function(req, res){
res.render('index', {
location: 'Documentation',
number: doc()
});
};
I am currently also calling the values for the "number:" JSON value from within the index.js as well.
var doc = (function() {
//do javascript and data calls
return a;
});
var bio = (function() {
//do javascript and data calls
return a;
});
var interview = (function() {
//do javascript and data calls
return a;
});
My question is: What would be the recommended way to do this or am I on the right track?
This will work as long as the functions doc(), bio(), and interview() are synchronous, but most likely that won't be the case, particularly if they need to perform some database access.
If these functions were async then your could should look like this:
exports.docs = function(req, res){
// call the doc() function and render the response in the callback
doc(function(err, number) {
res.render('index', {
location: 'Documentation',
number: number
});
});
};
The doc() function will look like this:
var doc = (function(callback) {
// This code very likely be async
// therefore it won't return any value via "return"
// but rather calling your function (callback)
db.doSomething(someparams, callback);
});
Inside db.doSomething() there will be a call to your function callback(err, theValue)
The asynchonous way would be something like:
exports.docs = function(req, res) {
fetchSomeValueFromMysql(req.param('foo'), function (err, data) {
if (err) {
res.send(500, 'boom!');
return;
}
res.render('index', {
location: 'Documentation',
number: data
});
});
};
Say if you had an async operation in bio();
exports.bio = function (req, res) {
bio(function (err, data) {
if (!err && data) {
res.render('index', {
location: 'Biometrics',
number: data
});
} else {
// handle error or no data
res.render('error');
}
});
}
var bio = function(cb) {
//do javascript and data calls
cb(err, data);
});
Again, there are many ways to get this working. But the above should do.
I am building an Express Node.js appication using this Facebook SDK, and here is my current route for root:
app.get('/', Facebook.loginRequired(), function (req, res) {
req.facebook.api('/me', function(err, user) {
res.render('index', {title: 'title', user: user});
});
});
I am making a call to the FB open graph URL http://graph.facebook.com/me, but I'd like to make multiple open graph calls to pass to my template. For example:
var APIcalls = ['/me', '/me/music', '/me/music.listens'];
req.facebook.api(APIcalls, function(err, res1, res2, res3){
res.render('index', {res1: res1, res2: res2, res3: res3});
});
What is the easiest way to make multiple API calls to Facebook and then pass the responses to my template accordingly? You can see my full app.js file here. Thanks!
This scenario seems perfect for async.map
see here: https://github.com/caolan/async#map
the async.map method takes an array of items and performs the same async call for each item in your array in parallel. If no errors are encountered, it will call a callback with a new array of results.
so your example would look something like this:
var async = require('async'),
APIcalls = ['/me', '/me/music', '/me/music.listens'];
async.map(APIcalls,
function(item, callback){
req.facebook.api(item, function(err, user) {
if(err){
return callback(err);
};
callback(null, user);
});},
function(err, results){
if(err){
throw new Error(err);
}
res.render('index', results);
}
);
For this you should use batching so that you can get away with only a single request to the Graph API.
FB.api('/', 'POST', {
batch: [{
relative_url: '/me'
},{
relative_url: '/me/music'
},{
relative_url: '/me/music.listens'
}]
}, function(o) {
console.log(o);
});
Alternatively you can use multi-fql
FB.api('/fql', {
q1: 'select ....',
q2: 'select ...'
}, function(o) {
console.log(o);
});
Once that is done, you simply combine the data into a single model that you pass to the renderer.