res.render ONLY after multiple data queries have finished - javascript

On my app.get in my server.js, I am returning a data set from mongo db, and then rendering my page whilst passing the data to it.
as shown bellow:
//page load
app.get('/', (req, res) => {
//find all data in test table
var articles;
db.collection('test').find().toArray((err, result) => {
if (err) return console.log(err)
articles = result
// renders index.ejs and passes result to quotes
res.render('./pages/index.ejs', {
quotes: articles
})
})
})
I want to be able to put multiple db queries, and then pass multiple variables of data to my render, my problem is that when I put my res.render outside of a db query, it tries to render before the db gets its data set.
see what I tried bellow:
//page load
app.get('/', (req, res) => {
//find all data in test table
var articles;
db.collection('test').find().toArray((err, result) => {
if (err) return console.log(err)
articles = result
})
// renders index.ejs and passes result to quotes
res.render('./pages/index.ejs', {
quotes: articles
})
})
my question is:
How could I make sure that the render happens ONLY after my db queries have run and returned the data to a variable?
Ultimately I would want to be able to do something like this:
//page load
app.get('/', (req, res) => {
//find all data in table 1
var AAA;
db.collection('test1').find().toArray((err, result) => {
if (err) return console.log(err)
AAA = result
})
//find all data in table 2
var BBB;
db.collection('test2').find().toArray((err, result) => {
if (err) return console.log(err)
BBB = result
})
//find all data in table 3
var CCC;
db.collection('test3').find().toArray((err, result) => {
if (err) return console.log(err)
CCC = result
})
// renders index.ejs and passes result to quotes
res.render('./pages/index.ejs', {
quotes: AAA,
quotes2: BBB,
quotes3: CCC
})
})
Any help or advice on this is appreciated. Thank you in advance.

Try this code, it's not tested but I think it should work:
app.get('/', (req, res) => {
var list = {};
db.collection('test').find().toArray().then(result => {
list.result = result;
}).then(() => {
return Promise.resolve(db.collection('foo').find().toArray());
}).then(result2 => {
list.result2 = result2;
}).then(() => {
return Promise.resolve(db.collection('bar').find().toArray());
}).then(result3 => {
list.result3 = result3;
}).then(() => {
res.render('./pages/index.ejs', list);
}).catch(e => {
console.error(e);
});
});
Update: we can use async/await for any method that returns Promise so the code would be cleaner:
// you can use express-async-errors package to make routes async
app.get('/', async(req, res) => {
let list = [];
list.result = await db.collection('test').find().toArray();
list.result2 = await db.collection('foo').find().toArray();
list.result3 = await db.collection('bar').find().toArray();
res.render('./pages/index.ejs', list);
});

You can use async module for that. Use async.parallel for functions which are independant of each other and use async.series for dependent processes. Check async.

Related

Can't get result from a nodejs module

I wrote a small Javascript module to get rows from a MongoDB database:
async function getAll() {
await mongoose.connect(config.mongoURL).catch(err => { console.log(err); });
const conn = mongoose.connection;
conn.collection('healthdata')
.find({}).toArray().then(result => {
console.log('=>Docs:', result);
return result;
}).catch (err => {
console.log(err);
}).finally(() => {
conn.close();
});
}
exports.getAll = getAll
I get the correct display from the console ("=>Docs:").
But, when I want to get the result from the following calling function, I get the Undefined value ("Result:"):
app.get("/", async (req, res) => {
var docs = await db.getAll().then();
console.log("Result:", docs);
res.render("index.html");
});
What is missing? Wrong?
you should not mix promises with async/await, so either the answer provide by G-Force but remove the await before db.getAll() or use var docs = await db.getAll(); this should solve your problem.
Bro if you are using mongoose follow these to get data from mongoose schema
schema.find().exec().then(result=>{
console.log(result)
}).catch(error=>{
console.log(error)
})
here all schema data in result that we console if any error come it displays in console.log(error)
I think the console.log(Result:...) line is executing before your getAll completes.
I would wrap it inside the "then" clause:
app.get("/", async (req, res) => {
const docsReturned = await db.getAll()
.then(docs => {
console.log("Result:", docs);
res.render("index.html");
})
.catch(err => {
console.error(err);
}));
return(docsReturned);
I also suggest adding a catch for any errors...good practices. :)

make two db calls and render the content of two different array populated by the results in one ejs page

I'm trying express and I have to render a page passing two arrays that is populated by two DB calls,
the query is correct, in fact, if I render only one array in the ejs page it passes the content.
When i pass a single array i do this :
dao.getAllSerie().then((show) => {
res.render('index', {
series: show,
})
}).catch(() => res.status(500).end());
If I do this everything goes well and nothing is wrong, content is rendered and ejs page is filled with value.
The demand is: "If I have to render two arrays that is filled with two results of two different call to the DB how can I do?"
I've tried this:
app.get('/', (req, res) => {
series = [];
categories = [];
//CALL TO A FUNCTION THAT SELECT FROM DB ASSIGN THE RESULT TO 'SERIES'
dao.getAllSerie().then((show) => { series = show; })
.catch(() => res.status(500).end());
//CALL TO A FUNCTION THAT SELECT FROM DB ASSIGN THE RESULT TO 'CATEGORIES'
dao.getCategorie().then((category) => { categories = category; })
.catch(() => res.status(500).end());
//IN THE RENDER PAGE EACH 'CATEGORIES' AND 'SERIES' ARE NULL
res.render('index', {
series: series,
categories: categories
})
})
But either series and categories results null which means that the ejs page is rendered without dynamics content.
I think is some sort of async problem, somebody know if it's possible and how?
Here the db call functions :
//GET OF ALL SERIES IN THE DB
exports.getAllSerie = function() {
return new Promise((resolve, reject) => {
const sql = 'SELECT * FROM serie';
db.all(sql, (err, rows) => {
if (err) {
reject(err);
return;
}
resolve(rows);
});
});
};
//GET OF ALL CATEGORIES IN THE DB
exports.getCategorie = function() {
return new Promise((resolve, reject) => {
const sql = 'SELECT DISTINCT categoria FROM serie';
db.all(sql, (err, rows) => {
if (err) {
reject(err);
return;
}
resolve(rows);
});
});
};
I know that categories should be in a separated table but it's just to make some try.
Thank you in advance to all for the time. Really grateful
You need to store your 2 asynchronous functions in an array then use Promise.all to wait for all asynchronous calls to get back, THEN only render the page :
app.get('/', (req, res) => {
let dataFetchers = []
dataFetchers.push(dao.getAllSerie) // the first async function
dataFetchers.push(dao.getCategorie) // the second
Promise.all(dataFetchers) // will wait for ALL promises to resolve or reject
.then(dataArray => {
let series = dataArray[0] // the first promise's result
let categories = dataArray[1] // the second
res.render('index', { // render only once you have ALL the datum
series: series,
categories: categories
})
})
.catch(err => res.status(500).end())
})

How to wait for MongoDB results in multiple nested loops

I am trying to load a certain page from my website only after the data is loaded from the database. However, it prints the results too early, giving an empty array, and it doesn't group the names at all. I believe this may be solved with async and await functions or callbacks, but I'm new to those and despite what I've searched, I still can't find the solution. So far, I've just been messing around with wrapping certain loops or functions in async functions and awaiting them. Other solutions have been focused on one .find() or other loop. Any help is appreciated! Thank you!
Routes.js:
app.get("/test", async (req, res) => {
var result = [];
users.findById(mongo.ObjectID("IDString"), async (err, res) => {
result = await res.getGroups(users, messages);
});
console.log(result);
//this should print [["name1"]["name1", "name2"],["name3", "name1"]]
res.redirect("/");
});
getGroups:
UserSchema.methods.getGroups = async function(users, messages) {
var result = [];
await messages.find({Group: this._id}, (err, group) => {
for(var i = 0; i < group.length; i++){
var members = group[i].Group;
var subArray = [];
members.forEach((memberID) => {
users.findById(memberID, (err, req) => {
console.log(req.name);
subArray.push(req.name);
console.log(subArray);
});
});
//After function finishes:
console.log(subArray);
result.push(subArray);
subArray = [];
}
});
return result;
};
In your first code snippet, you must set
console.log(result);
//this should print [["name1"]["name1", "name2"],["name3", "name1"]]
res.redirect("/");
in the callback function of findById. Like this:
app.get("/test", async (req, res) => {
var result = [];
users.findById(mongo.ObjectID("IDString"), async (err, res) => {
result = await res.getGroups(users, messages);
console.log(result);
//this should print [["name1"]["name1", "name2"],["name3", "name1"]]
res.redirect("/");
});
});
This is because of asynchronous nature of Javascript. Read the article below
https://medium.com/better-programming/is-javascript-synchronous-or-asynchronous-what-the-hell-is-a-promise-7aa9dd8f3bfb
If you want to use async/await paradigm, you can write this code like this:
app.get("/test", async (req, res) => {
var result = [];
result = await users.findById(mongo.ObjectID("IDString"));
console.log(result);
//this should print [["name1"]["name1", "name2"],["name3", "name1"]]
res.redirect("/");
});
In your second code snippet, you should use await OR callback. Just as explained above.

MongoDB: Return query from seperate file

I don't know if it is possible or maybe I don't know how to quite google the question properly but I am wondering is their a way to query the MongoDB from one file and return the results to another using node. Lets say I have two files routes.js and helpers.js
routes.js
const finder = require('../db_helpers/userHelpers');
exports.getIndex = (req, res, next) => {
finder.findUser()
.then(user => {
if (!user) {
return res.redirect('/signup')
}
res.render('shop/landing', {
pageTitle: 'landing'
});
})
.catch(err => {
console.log(err)
})
};
helpers.js
const User = require('../models/user');
exports.findUser = (user) => {
User.findOne()
.then(user => {
console.log(user);
return user
})
.catch(err => {
return err
})
};
This is what I have been working with for a few hrs now changing things around and such but to no avail. Like I said I may have been googling wrong but if someone could point me in the right direction or tell me this isn't possible that would be greatly appreciated.
The problem is that you are expecting a promise with finder.findUser().then() in the routes.js file but not returning a promise in userHelpers.js, so the then statement never invoked since the promise is never met.
Your userHelpers.js file should look like:
const User = require('../models/user');
exports.findUser = user => {
User.findOne((err, user) => {
if (err) return Promise.reject(err);
return Promise.resolve(user);
});
};

How to perform a callback in a promise?

I have here a function which gets a Section. It returns a Promise
router.get('/menu_section', (req, res) => {
Section.read(req.body)
.then(d => {
send(d, res);
})
.catch(e => {
error(e, res);
});
});
Is there a way, that while I handle the Promise I can cut down on the then catch boilerplate code? I am looking to write it in this way to reduce boiler plate.
router.get('/menu_section', (req, res) => {
Section.read(req.body).respond(data, err, res)
});
//handle it in this way for all routes.
function respond(data, err, res){
if(err) res.data({err})
else res.json({data});
}
EDIT: I want to avoid writing then catch altogether for every Promise handle
You wouldn't be able to do exactly what you mentioned (without overriding Promise, which is generally frowned upon).
You could however create a simple wrapping function to do it for you:
function respond(promise, res) {
promise
.then(data) => res.data(data))
.catch(err => res.data({err})
}
router.get('/menu_section', (req, res) => {
respond(Section.read(req.body), res);
});
You could even boil this down a bit to something like this:
function respond(getData) {
return (req, res) => {
getData(req)
.then(data) => res.data(data))
.catch(err => res.data({err})
};
}
router.get('/menu_section', respond(req => Section.read(req.body)));
With the second approach, you're basically just providing a function which gets the data, then it'll take that and process it in a standard way. It'll also create a function for taking req and res itself.
Maybe with currying?:
const respond = res => data => {
res.json(data);
};
So you can do:
router.get('/menu_section', (req, res) => {
Section.read(req.body).catch(e => e).then(respond(res));
});
However then i would directly make a middleware out of it:
const respond = (req, res, next) => {
Section.read(req.body).then(
data => res.json(data),
err => next(err)
);
};
So you can do
router.get('/menu_section', respond);
Note that async / await can be helpful here:
router.get('/menu_section', async (req, res) => {
try {
res.json(await Section.read(req.body));
} catch(e) {
res.json(e);
}
});
If you're looking to respond to all routes in this manner, you could use a Middleware function and use it application wide.
app.use((req, res, next) => (
Section.read(req.body)
.then(d => send(d, res))
.catch(e => error(e, res));
)
You can also use such function on a per-route basis, or even per-file (containing multiple routes).

Categories

Resources