This is how I am currently getting data from mongodb:
users.get(base_URL, (req, res) => {
UserModel.find({}, (err, docs) => {
res.render("Users/index", {
title: "All Users here",
user_list: docs
});
});
});
Now, as you can see this is an express application. What I would like, is to simple call a function so that I can get the value from the docs variable inside the mongodb model callback. How do I do this, ideally, I want to see something like this:
users.get(base_URL, (req, res) => {
res.render('<some_jade_file_here>', {
title: "Yes, got it right",
user_list: getAllUsers();
});
});
Ideally, I just want to call a function. How can I do this, since having to put render inside of a mongodb call is a problem, since you may want to query a bunch of things from the database, and it might not even be just one database. I'm struggling a little since I'm not all that used to callbacks.
Any help would be deeply appreciated. If you're wondering about this syntax () => {}, thats just an anonymous function in typescript.
You can't do it without callbacks, but you can use an async flow control library like async to help manage the nest of callbacks. In this case you probably want to use async.parallel.
Using that you can do something like:
users.get(base_URL, (req, res) => {
var data = {
title: "Yes, got it right"
};
async.parallel([
(callback) => {
UserModel.find({}, (err, docs) {
data.user_list = docs;
callback(err);
});
},
(callback) => {
// Other query that populates another field in data
}
], (err, results) => {
// Called after all parallel functions have called their callback
res.render('<some_jade_file_here>', data);
});
});
Related
I am trying to display the number of documents in my MongoDB database whenever the user retrieves the homepage of my web application. The following diagram shows how I wanted to implement this: https://liveuml.com/view/5db6af5e663178088afee61e
Here is the relevant code snippet for the Router
app.route('/')
.get(
(req, res, next) => {
res.locals.countOfWords = countAllWords();
next();
},
(req, res, next) => {
renderIndex(req, res);
}
);
And the relevant code snippet for the Controller
function countAllWords() {
myModel.estimatedDocumentCount({}, (err, result) => {
return result; // returns an object instead of an integer
});
}
function renderIndex(req, res) {
res.render('index', {
'countOfWords' : res.locals.countOfWords
});
}
However, the result that the Controller returns is a Query object and not an integer. So, I am seeing There are [object Object] documents in your database on the web page instead of something like There are 12 documents in your database.
What makes it even more confusing to me : When I replace the return result statement with console.log(result), I see the expected number in the console.
function countAllWords() {
myModel.estimatedDocumentCount({}, (err, result) => {
console.log(result); // displays the number as expected
});
}
My question is, how can ensure that I pass the number back to the Router instead of an object so that it can be displayed on the web page ?
I am using the latest versions of NodeJS, ExpressJS and Mongoose.
Many thanks for your help.
simply use async await and count() in mongoose:
async countAllWords()=>{
let countOfmymodel = await userModel.count({})
}
I want to get the entry at index gameNumber in the history collection. From that entry I want to take team1Player1 and find that player in the rating collection and then set his games to an arbitary number. This code works sometimes but idk what makes it not work
app.post("/delete", function (req, res) {
var gameNumber = req.body.test;
History.find({}, async function (err, historyRecords) {
if (err) {
console.log(err);
return;
}
await Rating.updateOne({name: historyRecords.reverse()[gameNumber-1].team1Player1}, { games: 9999});
})
// res.redirect("/delete");
});
Before answering your question, I want to note a few issues with your code.
You are mixing callback style functions with async await (promise based). You are using await on Rating, why not do it on History as well?
Your are using an expressive db which allows you to do complex queries, use it to your advantage.
Due to your passing a callback, but trying to render /delete at the end, the /delete will execute before your callback is invoked due to the callbacks async nature. In order for your code to complete, you either go full callback style on everything, or promise based, or of course, async await, which is syntactic sugar on top of promises.
Lets query the db for the lists at index: index, take note that I used limit. In your example you take all of them into memory, both wasting resources + network bandwidth, always limit your queries!
Here is an example with async await:
app.post("/delete", async function (req, res, next) {
var gameNumber = req.body.test
const [ gameRetrieved ] = await History.find({ gameNumber }).limit(1)
await Rating.updateOne({ name: gameRetrieved.team1Player1 }, { games: 9999 })
});
Here is an example with callbacks:
app.post("/delete", async function (req, res, next) {
var gameNumber = req.body.test
return History.find({ gameNumber }).limit(1).exec((err, [ gameRetrieved ]) => {
return Rating.updateOne({ name: gameRetrieved.team1Player1 }, { games: 9999 }).exec((err, rating)=>{
res.render('/delete')
})
})
});
I did not account for error handling as that is not the scope here.
To sum things up: a callback/promise is async, doing anything outside of its scope will execute before/after it depending on the situation, which in your case sometimes finished before rendering delete and sometimes did not.
I'm trying to create a webpage where there is an instance of all the current Projects I am working on, on the left, so I'd need a .forEach() function in order to loop through all of them in order to display it, but on the other side, I need to display the information that is currently selected.
Please first take a look at my code block so I can try to explain the thought process behind what I was trying to do.
So I didn't have any problems selecting the information of the single project that I needed to display on this webpage. I used the .findOne() function in order to pick out the information that I needed.
The problem that I'm facing is that I also need to pass a var that's connected to the .find() function in order to pass through all of the elements of the database. The way I went about this is that I thought I would be able to set the definition of allProjects by manually running the .find() function, and then returning it, thus assigning Projects.find() to allProjects.
app.get('/projects/:url', (req, res) => {
Projects.findOne({ Url: req.params.url }, (err, foundProject) => {
if (err) {
console.log(err);
} else {
res.render('show', {
foundProject: foundProject,
allProjects: Projects.find({}, (err, allProjects) => {
if (err) {
res.send('error');
} else {
return allProjects;
}
})
});
}
});
});
I thought that by returning allProjects and then also having that assigned to allProjects, i'd be able to use the allProjects variable in my show.ejs page.
Unfortunately, I'm getting an error 'allProjects.forEach() is undefined' which leads me to believe that in the app.js where I am defining allProjects, it's not being assigned the correct value that I want it assigned.
It looks like you're expecting return allProjects to do something, but that's actually ignored. Unless you have a callback function you can call, that will go into the void and never be seen by anyone. This is true of virtually all callback functions. They do not care what value that function returns because it's never relevant, what they want is the future value which comes through the callback given to this function.
In other words it plays out like this:
asyncFunctionTakingCallback(function(cb) {
cb(null, value); // This is the important value!
return value; // Nobody cares about this value. Don't even bother.
});
To fix that you need to move the render call inside of the inner-most callback function:
app.get('/projects/:url', (req, res) => {
Projects.findOne({ Url: req.params.url }, (err, foundProject) => {
if (err) {
console.log(err);
// Return here to avoid another level of indentation below
return;
}
Projects.find({}, (err, allProjects) => {
if (err) {
res.send('error');
} else {
res.render('show', {
foundProject: foundProject,
allProjects:
});
}
});
});
});
Now that's still a dizzying amount of code and the nesting here is getting completely out of control even though this is relatively simple Node code.
For comparison here's a version that uses async functions:
app.get('/projects/:url', async (req, res) => {
let foundProject = await Projects.findOne({ Url: req.params.url });
res.render('show', {
foundProject: foundProject,
allProjects: await Projects.find({})
});
});
There's really not much to it this way. What await does is basically stall out on that line and wait for the promise to get resolved or produce an error. Any errors produced should be captured with try { ... } catch as usual.
I know node is all about async stuff but I want to do things in serial mode as follows:
make api request > convert body xml to JSON.stringify > pass string to template.
request.get({url:url, oauth:oauth}, function(err, res, body){
parseString(body, function(err, result){
output = JSON.stringify(result);
res.render('home', { title: 'Fantasy Home',
output: output });
});
});
Now I want to do this in sequence, but with all the callbacks I'm confused.
res.render doesn't work nested inside callbacks because the res object doesn't exist. Having it outside won't work because it'll run before the callbacks execute so you'd get "undefined" for output.
There has to be a way to do things in sequential order. Why is everything a callback?? Why can't these functions just return a regular non-callback result?
How can I make this work?
The others fail to mention why your res.render does not work.
You probably have something like this:
app.get('/', function(req, res, next) { // You need the res here to .render
request.get({url:url, oauth:oauth}, function(err, res, body){ // But you are using this res instead, which does not have the render method
parseString(body, function(err, result){
output = JSON.stringify(result);
res.render('home', { title: 'Fantasy Home',
output: output });
});
});
});
Read the comments in the code. So your solution is, use res.render from the request handler, rename res in the request.get callback to something else.
You should use middlewares, also promises is better thing to work with async in node, but I'll show you with callbacks. It is strongly suggested to not to block your thread with synchronous calls! Since node.js is single threaded. next() is an callback here so middleware won't allow execution of main route function (with res.render) until next() is called. You can pass as many middlewares as you wish.
app.use('/user/:id', middleware, (req, res) => {
//here you'll find your data
console.log(req.callData);
res.render(view, req.callData);
}
middleware(req, res, next) => {
dotheCall(dataToPass, (err, cb) => {
req.callData = cb;
// remember about handling errors of course!
return next();
})
}
JavaScript is single threaded if we use synchronous code then that will itself a big problem around response time (node.js) and all. Everything is implemented with callback fashion due to the benefit of event loop.
You can take a deep understanding of event loop : https://youtu.be/8aGhZQkoFbQ (Very good explaination)
You can use Promisification for the scenario you want to implement : http://bluebirdjs.com/docs/getting-started.html
request.get({url:url, oauth:oauth}, function(err, res, body) {
// Note :: This is Promisified Function
return parseString(body)
.bind(this)
.then(function(result) {
output = JSON.stringify(result);
res.render('home', {title: 'Fantasy Home', output: output });
return true;
})
.catch(function(error)
{
// Error Handling Code Here
// Then send response to client
});
});
You can implement promisified function using the following approach
function parseString(body) {
var Promise = require("bluebird");
return new Promise(function(resolve,reject) {
// Your Parsing Logic here
if(is_parsed_successfully) {
return resolve(parsed_data);
}
return reject(proper_err_data);
})
}
Each time I throw a query, the nest depth increase by one, just like the code below. If I knew how to define a query as a function not in the action, the readability of my code would increase.
exports.getAll = function (req, res) {
client.query('SELECT * FROM tag', function (err, result, fields) {
client.destroy();
if (err) {
throw err;
}
var tag = result[0].tag;
client.query('SELECT COUNT(follow_id) AS following_tag_num FROM follow WHERE user_id = ?', [req.session.user.user_id], function (err, result, fields) {
client.destroy();
if (err) {
throw err;
}
res.render('hoge', {
title: 'Welcome to Hoge',
userInfo: req.session.user,
tag: tag,
following_tag_num: result[0].following_tag_num
});
});
});
}
Just make the handler a named function:
client.query(
'SELECT COUNT(follow_id) AS following_tag_num FROM follow WHERE user_id = ?',
[req.session.user.user_id],
handleResult
);
function handleResult(err, result, fields) {
client.destroy();
if (err) {
throw err;
}
res.render('hoge', {
title : 'Welcome to Hoge',
userInfo : req.session.user,
tag : tag,
following_tag_num: result[0].following_tag_num
});
}
You might look into several node flow control modules that are available to help curb the nesting. I like one called async. It provides a variety of ways to de-nest your nested code.
var async = require('async');
async.waterfall([
function(callback) {
client.query(sql, callback);
},
function(results, callback) {
// do something with results, then call callback
}],
function(err, data) {
// if any errors occur above, err is not null
// otherwise 'data' is whatever got passed to the last callback
});
async.waterfall takes a list of functions, and passes the results of each one on to the next, finally calling the second parameter, another function, with the final result. Results are passed not by returning them, but by a callback function. async also supports running several functions in parallel, in series, and a variety of different common patterns used in node.