I have a dashboard view ( dashboard.jade ) that will display two panels with different information, all that info should be retrieved from a database and then sent to the view.
Let's say i have a route file ( document.js ) with two actions defined:
exports.getAllDocuments = function(req, res){
doc = db.model('documents', docSchema);
doc.find({}, function(err, documents) {
if (!err) {
// handle success
}
else {
throw err;
}
});
};
exports.getLatestDocumentTags = function(req, res){
tags = db.model('tags', tagSchema);
tags.find({}, function(err, docs) {
if (!err) {
// handle success
}
else {
throw err;
}
});
};
These functions would only serve the porpuse of retrieving data from the database.
Now i would like to send that data to the dashboard view from my dashboard.js route file under exports.index function where i render my dashboard view.
The problem is, since the db calls will be async i wouldn't have access to the data before i could call the view.
I guess i could have an action that simply did all my db calls and through callbacks deliver all the data at once to the view but that would make my data retrieval actions not reusable.
I'm really confused on how to tackle this problem correctly, probably i'm getting this async thing all wrong. Can someone give me some hints on how to do this properly ?
Here's something to pique your interest.
//Check out the async.js library
var async = require('async');
//Set up your models once at program startup, not on each request
//Ideall these would be in separate modules as wel
var Doc = db.model('documents', docSchema);
var Tags = db.model('tags', tagSchema);
function index(req, res, next) {
async.parallel({ //Run every function in this object in parallel
allDocs: async.apply(Doc.find, {}) //gets all documents. async.apply will
//do the equivalent of Doc.find({}, callback) here
latestDocs: async.apply(Tags.find, {})
], function (error, results) { //This function gets called when all parallel jobs are done
//results will be like {
// allDocs: [doc1, doc2]
// latestDocs: [doc3, doc4]
// }
res.render('index', results);
});
}
exports.index = index;
};
Try some more tutorials. If you haven't had the "a ha" moment about how async programming works in node, keep going through guided, hand-held tutorials before trying to write brand new programs without guidance.
//Check out the async.js library and mangoose model
var mongoOp = require("./models/mongo");
var async = require('async');
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})
});
Related
I have an api server and some script jobs. They are using the same function to pull a roster using mongoose and populate the players in the roster.
On the api server, this function is called normally. Using the script, it doesn't.
API example
function getRoster(id) {
var deferred = Q.defer();
Roster.find({_id:id}, 'playerRoster userId tournamentId').populate('playerRoster').exec(
function(err, roster) {
if (err) {
deferred.resolve(err);
}
deferred.resolve(roster[0]);
});
return deferred.promise;
}
api.post('/get_roster', function(req, res) {
// get tournament
var id = req.body._id;
var playerId = req.body.playerId;
getRoster(id).then(function(data) {
var roster=data;
res.json(roster);
});
});
Script
module.exports = function(config) {
this.getRoster=function(id) {
//return Roster.find({_id:id}, 'playerRoster userId tournamentId').exec( - THIS RETURNS
return Roster.find({_id:id}, 'playerRoster userId tournamentId').populate('playerRoster').exec(
function(err, roster) {
if (err) {
return err;
}
console.log('roster[0]',roster);
return roster[0];
});
}
this.tallyPoints = function(tournamentPlayer,sportsPlayers) {
var deferred = Q.defer();
var totalPoints =0;
console.log("tallyPoints 0 ",tournamentPlayer);
var rosterId = tournamentPlayer.player.roster[0];
console.log("tallyPoints 1 ",rosterId);
this.getRoster(rosterId).then(function(roster2){
console.log("tallyPoints 2 ",roster2);
...
deferred.resolve(totalPoints);
});
return deferred.promise;
}
return this;
};
In the script, neither logging for the roster[0] or tallyPoints 2 lines print, but there is no error either.
Why doesn't Roster.find return when I add populate? The only thing I can imagine is because playerRoster collection has 2000 records searching for ~10 and it hits some timeout that isn't being caught.
Any suggestion to clean it up is also appreciated.
Thanks
Moongoose supports promises for a long time. It's unsuitable to use callback-based Mongoose API where promises are desirable and the use of Q.defer with existing promises is known as Deferred antipattern (similarly, new Promise results in promise construction antipattern).
In its current state getRoster doesn't return a promise and doesn't handle errors correctly.
function getRoster(id) {
return Roster.find({_id:id}, 'playerRoster userId tournamentId').populate('playerRoster').exec()
.then(roster => roster[0]);
}
api.post('/get_roster', function(req, res) {
// get tournament
var id = req.body._id;
var playerId = req.body.playerId;
getRoster(id)
.then(function(data) {
var roster=data;
res.json(roster);
})
.catch(err => {
// handle error
});
});
Considering that only roster[0] is used, it likely should be changed to Roster.findOne.
It doesn't matter whether getRoster is used in Express route or elsewhere, it should work. It's unknown how module.exports = function(config) {...} module is used, but this may refer to wrong context if it isn't used as class. If getRoster and tallyPoints don't use config, they shouldn't reside inside this function.
I have a big problem.
I want to iterate over collection a result set and for each set i want to find one result.
This looks like this:
router.get('/', function(req, res) {
var floors = [];
var rooms = [];
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
});
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
req.db.collection('floors').findOne({_id: new ObjectID(room.floorId)}, function(error, floor) {
room.floor = floor;
rooms.push(room);
});
});
res.render('rooms', { floors: floors, rooms: rooms });
});
The Problem is that the page will be rendered before the iteration is complete.
I tried to use async and promises, but i didn't get it to run.
Basically you have to wait until all your queries are done before sending the rendering result. Unfortunately you don't use promises so this will get a bit messy.
It appears that you are using the native client and according to the docs there is a second callback that gets called when all iterations are done
http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#forEach
router.get('/', function(req, res, next) {
var floors = [];
var rooms = [];
function done(err){
if(err) {
return next(err);
}
res.render('rooms', { floors: floors, rooms: rooms });
}
function getRooms(err){
if(err){
return next(err);
}
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
// you already have all the floors, no need to hit the db again
floors.find(floor => floor._id === room.floorId); // not sure about this 100% as _id could be an object
}, done);
}
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
}, getRooms);
});
to be noted that this request will get quite heavy when your db grows.
I am a nodejs beginner and I ran into some callback behaviour that I don't understand. I am using an Express router to write Mongoose objects to Mongodb using a POST request. In the body of the request I am passing in a nested json structure with two fields - jobDetails and examples. The data in jobDetails is used to create a Job Mongoose object and the data in examples is used to create several Example Mongoose objects. The Job and Example objects are linked in that a Job contains a list of Example objects in one of it's fields.
The way I tried to implement this was with callbacks in the following way. Basically I save the Job object to mongo first, then iterate over the examples - each time creating an Example object and linking it to the job via the .job field and also saving the Example object to mongo. Then in the callback to the Example object save function I updated the Job object with the new Example object and saved the updated version to mongo.
router.post('/jobs', function (req, res, next) {
var job = new Job(req.body.jobDetails);
var examples = req.body.examples;
console.log("JOB DETAILS");
console.log(req.body.jobDetails);
console.log("EXAMPLES");
console.log(req.body.examples);
//save job
job.save(function (err, job) {
console.log(err);
});
//save examples
for(i=0; i<examples.length;i++){
var eg = new Example({content: examples[i]});
eg.job=job;
eg.save(function (err, eg){
job.examples.push(eg);
job.save(function(err, job){
console.log(err);
});
console.log(err);
});
}
});
This did not perform as I would expect. Specifically, double the number of examples were actually saved to mongo with several duplicates and some missing. I understand that callbacks are asynchronous but to me that still doesn't explain why double the number of examples would be saved and some would be duplicated and some would be missing.
I eventually got it to work correctly without using callbacks at all in the following way.
router.post('/jobs', function (req, res, next) {
var job = new Job(req.body.jobDetails);
var examples = req.body.examples;
console.log("JOB DETAILS");
console.log(req.body.jobDetails);
console.log("EXAMPLES");
console.log(req.body.examples);
//save job
job.save(function (err, job) {
console.log(err);
});
//save examples
for(i=0; i<examples.length;i++){
var eg = new Example({content: examples[i]});
eg.job=job;
eg.save(function (err, eg){
console.log(err);
});
job.examples.push(eg);
job.save(function(err,job){
console.log(err);
});
}
});
And I'm not sure if this is the optimal solution either. But I would like to know why my original approach resulted in the unintended behaviour.
This should work..
router.post('/jobs', function(req, res, next) {
var job = new Job(req.body.jobDetails);
var examples = req.body.examples;
console.log("JOB DETAILS");
console.log(req.body.jobDetails);
console.log("EXAMPLES");
console.log(req.body.examples);
//save job
job.save(function(err, result) {
if (!err) {
//save examples
for (i = 0; i < examples.length; i++) {
var eg = new Example({
content: examples[i]
});
eg.job = job;
eg.save(function(err, eg) {
job.examples.push(eg);
job.save(function(err, job) {
if (!err)
job.examples = [];
});
console.log(err);
});
}
}
});
});
I would suggest you use a library like async to perform such save operations in a step by step manner. Follow this approach for better readability of code and better results
var async = require('async');
router.post('/jobs', function(req, res, next) {
var job = new Job(req.body.jobDetails);
var examples = req.body.examples;
var savedExamples = [];
console.log("JOB DETAILS");
console.log(req.body.jobDetails);
console.log("EXAMPLES");
console.log(req.body.examples);
async.eachSeries(examples, function iteratee(example, callback) {
var eg = new Example({
content: example
});
eg.job = job;
eg.save(function(err, savedEg) {
if(!err) {
savedExamples.push(savedEg);
}
callback(err)
});
}, function(err) {
if(err) {
//handle errors
}
job.examples = savedExamples;
job.save(function(err,job) {
if(err) {
//handle errors
}
//success callback
});
});
});
Using this approach you will have to call the save function for job only once after all other operations are completed. If an error is triggered at any point the whole flow is stopped. For more info regarding async library refer this!
I am creating a CRUD api with express and mongodb. I have a specific route which queries one collection in my mongo db and retrieves whatever documents match the query criteria. My program then loops through these documents and trys to find the latest cross entry in another collection in my db
exports.findLatestCommitforAllRepos = function(req,res,next){
var githubCommitDataCollection = index.db.collection(githubCommitDataCollectionName);
var enabledRepoCollection = index.db.collection(enabledRepoCollectionName);
var latestCommits = [];
enabledRepoCollection.find({enabled:true}).toArray(function(err,repos) {
if (err) { next(err); }
if (repos.length === 0 || repos === 'undefined') {
res.status(404);
res.send("There are no repos being tracked")
}
else {
repos.forEach(function(enabledRepo) {
var repo = enabledRepo.repo;
var userOrOrg = enabledRepo.userOrOrg;
githubCommitDataCollection.find({repo: repo, userOrOrg:userOrOrg}).sort({commitDate: -1}).limit(1).toArray(function(err,commit) {
if (commit.length === 0 || repos === 'undefined') {
res.send("No commit found for repo " + repo);
}
// console.log(commit[0]);
latestCommits.push(commit[0]);
console.log(latestCommits);
});
});
res.setHeader('Content-Type', 'application/json');
res.status(200);
res.json(latestCommits);
res.end();
}
});
}
This results in an empty array being returned.
You can use the async libary especially the async.waterfall() method when you need to run a tasks array of functions in series, each passing their results to the next in the array.
Consider the following example:
// Include the async package
// Make sure you add "async" to your package.json
async = require("async");
exports.findLatestCommitforAllRepos = function(req,res,next){
var latestCommits = [];
async.waterfall([
// Load all documents
function(callback) {
index.db.collection(enabledRepoCollectionName).find({"enabled": true}).toArray(function(err,repos){
if (err) return callback(err);
callback(null, repos);
});
},
// Get count of documents where price is empty
function(reposData, callback) {
async.each(reposData, function(enabledRepo, callback) {
index.db.collection(githubCommitDataCollectionName)
.findOne({repo: enabledRepo.repo, userOrOrg: enabledRepo.userOrOrg})
.sort({commitDate: -1}).limit(1)
.exec(function(err, commit) {
latestCommits.push(commit);
callback();
});
}, callback);
}
], function(err, result) { //This function gets called after the three tasks have called their "task callbacks"
if (err) return next(err);
res.setHeader('Content-Type', 'application/json');
res.status(200);
res.json(latestCommits);
res.end();
});
});
One minor suggestion in code,
use .findOne instead of .find
Means instead of
githubCommitDataCollection.find({repo: repo, userOrOrg:userOrOrg}).sort({commitDate: -1}).limit(1).toArray(function(err,commit) {
use
githubCommitDataCollection.findOne({repo: repo, userOrOrg:userOrOrg}).sort({commitDate: -1}).exec(function(err,commit) {
It will return only one commit and check console.log(commit) value to check what your are getting as result.
Or Please check share existing documents of githubCommitDataCollection
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