I am trying to run two different mongoose queries and display the result in same ejs page. I searched a lot and finally found the async method. How I tried this is below.
js file
router.get('/CreateSurvey', function(req, res, next) {
async.parallel([
function(callback) {
QBank.find( function ( err, classes, count ){
classes : classes
});
},
function(callback) {
QBank.findOne({_id:"H001"}, function ( err, questions, count ){
questions : questions
});
}
], function(err) {
if (err) return next(err);
res.render('CreateSurvey', QBank);
});
});
But when I refresh '/CreateSurvey' page it does not render. I have already downloaded async module and required in my js file. Where am I wrong?
Firstly, What is that classes : classes and questions : questions lines in your code? What are you trying to do?
The callback parameter of each task function (the functions in array) should be called inside the task to indicate completion of each task with success or error.
The optional main callback (the third function in your code), where you are doing the actual rendering, will be called only after all the tasks have completed.
In your code callback is not called inside the task. As a result the final callback is also never getting called. In fact, in your code only the first *task gets executed and nothing happens beyond that.
Read more about async.parallel in documentation
There are some issues with the code, you aren't calling callback corresponding to async call. Try the following:
router.get('/CreateSurvey', function(req, res, next) {
async.parallel({
classes: function(callback) {
QBank.find(function(err, classes, count){
callback(classes);
});
},
questions: function(callback) {
QBank.findOne({_id:"H001"}, function (err, questions, count ){
callback(questions);
});
}
}, function(err, result) {
if (err) return next(err);
res.render('CreateSurvey', result); // { classes : [c1, c2] , questions : [q1, q2] }
});
});
Related
I learn Node.js and doing a small site guided by MDN.
I reached to place where there using module async.
This is code
async.parallel({
book_count: function(callback) {
Book.countDocuments({}, callback); // Pass an empty object as match condition to find all documents of this collection
},
book_instance_count: function(callback) {
BookInstance.countDocuments({}, callback);
},
book_instance_available_count: function(callback) {
BookInstance.countDocuments({status:'Available'}, callback);
},
author_count: function(callback) {
Author.countDocuments({}, callback);
},
genre_count: function(callback) {
Genre.countDocuments({}, callback);
}
}, function(err, results) {
res.render('index', { title: 'Local Library Home', error: err, data: results });
});
};
This code count number documents in the database.
I don't understand what is argument "callback". There have nothing sent in these properties "book_count, book_instance_count, author_count, etc.." but all of these function work excellent.
Please, explain to me , what is odd argument "callback" and how to use it.
Callback function
A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.
function greeting(name) {
alert('Hello ' + name);
}
function processUserInput(callback) {
var name = prompt('Please enter your name.');
callback(name);
}
processUserInput(greeting);
https://developer.mozilla.org/en-US/docs/Glossary/Callback_function
I have variable number of async function to run. For two function, I can write like this:
async.parallel([
function(parallelCb) {
client.get('statuses/user_timeline', {screen_name: 'rajeevnodetest'}, function(error, tweets, response){
parallelCb(null, {error: error, tweets: tweets});
});
},
function(parallelCb) {
client.get('statuses/user_timeline', {screen_name: 'rajeev_jayaswal'}, function(error, tweets, response){
parallelCb(null, {error: error, tweets: tweets});
});
},
], function(error, results) {
// results will have the results of all 2
res.send(JSON.stringify(results));
});
How should I call variable number of function inside async.parallel ? I tried adding for loop inside async.parallel but it did not work. Any suggestion ?
I am assuming you will be reaching same api in order to retrive user tweets.
Therefore you have an array containing all tweeter handles:
var screenNames = ['rajeevnodetest', 'rajeev_jayaswal', ..., 'brandnewhandle'];
You can define a function to reach the API like this:
//Custom function to find user status list
var findStatus = function(handle, callback) {
client.get('statuses/user_timeline', {screen_name: handle}, function(error, tweets, response){
callback(null, {error: error, tweets: tweets});
});
}
Then you could use Asyn.map function in order to execute that function once for every element in your tweeter handle list like:
async.map(screenNames, findStatus, function(err, results) {
// results will have the results of all 2
res.send(JSON.stringify(results));
});
If you need to make calls to multiple API endpoints for each tweeter handle then you would need to use paralell and create an entry for each endpoint.
You can use async.times function Like this
function(parallelCb) {
async.times(50, function (n, next) {
client.get('statuses/user_timeline', {screen_name:'rajeevnodetest'},
function(error, tweets, response){
next(err, tweets);
});
}, function (error, results) {
// do something with your results
parallelCb(null, {error: error, tweets: results});
}
}
So if you want to Run n number of async function before calling another method You will have to use async.series method using async.times in it because according to async.parallel documentation it Run the tasks collection of functions in parallel, without waiting until the previous function has completed.
you should look into EventEmitter
call the emit method withing your async functions and hook all the function you want to run to that event name
I am learning Node.js; due to asynchronous of Node.js I am facing an issue:
domain.User.find({userName: new RegExp(findtext, 'i')}).sort('-created').skip(skip).limit(limit)
.exec(function(err, result) {
for(var i=0;i<result.length;i++){
console.log("result is ",result[i].id);
var camera=null;
domain.Cameras.count({"userId": result[i].id}, function (err, cameraCount) {
if(result.length-1==i){
configurationHolder.ResponseUtil.responseHandler(res, result, "User List ", false, 200);
}
})
}
})
I want to use result in Cameras callback but it is empty array here, so is there anyway to get it?
And this code is asynchronous, is it possible if we make a complete function synchronous?
#jmingov is right. You should make use of the async module to execute parallel requests to get the counts for each user returned in the User.find query.
Here's a flow for demonstration:
var Async = require('async'); //At the top of your js file.
domain.User.find({userName: new RegExp(findtext, 'i')}).sort('-created').skip(skip).limit(limit)
.exec(function(err, result) {
var cameraCountFunctions = [];
result.forEach(function(user) {
if (user && user.id)
{
console.log("result is ", user.id);
var camera=null; //What is this for?
cameraCountFunctions.push( function(callback) {
domain.Cameras.count({"userId": user.id}, function (err, cameraCount) {
if (err) return callback(err);
callback(null, cameraCount);
});
});
}
})
Async.parallel(cameraCountFunctions, function (err, cameraCounts) {
console.log(err, cameraCounts);
//CameraCounts is an array with the counts for each user.
//Evaluate and return the results here.
});
});
Try to do async programing allways when doing node.js, this is a must. Or youll end with big performance problems.
Check this module: https://github.com/caolan/async it can help.
Here is the trouble in your code:
domain.Cameras.count({
"userId": result[i].id
}, function(err, cameraCount) {
// the fn() used in the callback has 'cameraCount' as argument so
// mongoose will store the results there.
if (cameraCount.length - 1 == i) { // here is the problem
// result isnt there it should be named 'cameraCount'
configurationHolder.ResponseUtil.responseHandler(res, cameraCount, "User List ", false, 200);
}
});
I am using express and mongoose to list cats in my home page
app.get('/', function(req, res) {
Model.cats.find(function(err, ret) {
res.render('home.jade', {cats: ret});
})
});
Now lets say I wanted to display cats and dogs as well. How could I do it? The find function is async after all, so Im not sure how to prepare the response.
If you include the async npm module, it's quite simple:
app.get('/', function(req, res) {
async.parallel({
cats: function(callback) {
Model.cats.find(function(err, ret) {
callback(null, ret);
});
},
dogs: function(callback) {
Model.dogs.find(function(err, ret) {
callback(null, ret);
});
}
},
function(err, results) {
// results will be { cats: results, dogs: results}
res.render('home.jade', results);
});
});
The technique above starts two named async requests, one cat and the other dog. Each function calls your Models. When complete, they call the callback passing any errors (which I've just put as null above) and the results of the find. It's quite handy when using named calls this way as the results will be placed in two properties of a single object which you could pass directly to the render method.
given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view?
For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne. But you also want to display a list of recently logged in users.
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
})
}
// here, current_user isn't populated until the callback fires
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
});
};
So res.render is in the first query callback, fine. But what about waiting on the response from findOne to see if we know this user? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. Not pretty.
I can think of some workarounds -
make it really async and use AJAX on the client side to get the current user's profile. But this seems like more work than it's worth.
use Q and promises to wait on the resolution of the findOne query before rendering. But in a way, this would be like forcing blocking to make the response wait on my operation. Doesn't seem right.
use a middleware function to get the current user info. This seems cleaner, makes the query reusable. However I'm not sure how to go about it or if it would still manifest the same problem.
Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. So, what is the usual pattern given this type of requirement?
Yep, this is a particularly annoying case in async code. What you can do is to put the code you'd have to duplicate into a local function to keep it DRY:
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
function render() {
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
}
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
render();
})
} else {
render();
}
});
};
However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware.
Something like:
exports.loadUser = function (req, res, next) {
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
req.player = player
}
next();
})
} else {
next();
}
}
Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there.
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});
Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL.
http://expressjs.com/4x/api.html#app.param