Node js callback issue - node.js

In my NodeJS application, I am building an API that first fetches all tarrifs name from tarrifs collection then based on all those tarrifs I want to return the counts of these tariffs allocated to users I tried the below-given code
router.get('/getTarrifDetails', (req,res,next) => {
result=[];
tname=[];
counts=[];
Tarrif.find().distinct('tarrif_type', (err,docs) => {
docs.forEach((ele) => {
tname.push(ele);
User.countDocuments({tarrif_type:ele}, (uerr,usr) => {
counts.push(usr);
});
result.push(ele);
});
result.push(counts);
});
});
When I console.log(result) it only shows one array of tarrif_type and other array is empty

You need to understand how the event loop works. I was here once, and I made the same mistakes once.
Try to sync your callbacks since you want to sequentially, like so:
router.get('/getTarrifDetails', (req, res, next) => {
let result = [], count = 0;
Tarrif.find().distinct('tarrif_type', (err, docs) => {
async.forEach(docs, async ele => {
try {
let userCount = await User.countDocuments({ tarrif_type: ele });
result.push(userCount);
} catch (err) {
//your err goes here.
}
})
});
});
I am not sure this will work 100%, but try it out and debug a little bit.

Related

i want to return array of posts, but i got an empty array

postsArr does not get data
router.get('/user-post/:id', checkJwt, (req, res, next) => {
let postsArr = []
db.userSchema.findOne({ _id: req.params.id })
.populate('posts')
.exec((err, da) => {
for (let i = 0; i < da.posts.length; i++) {
db.postSchema.find({ _id: da.posts[i]._id })
.populate('comments')
.exec((err, post) => {
postsArr.push(post)
})
}
console.log(postsArr)
})
})
This is a whole lot easier if you use the promise interface on your database:
router.get('/user-post/:id', checkJwt, async (req, res, next) => {
try {
let da = await db.userSchema.findOne({ _id: req.params.id }).populate('posts').exec();
let postsArray = await Promise.all(da.posts.map(post => {
return db.postSchema.find({ _id: post._id }).populate('comments').exec();
}));
res.json(postsArray);
} catch (e) {
console.log(e);
res.sendStatus(500):
}
});
The challenge with an asynchronous operation in a loop is that they don't run sequentially - they all run in parallel. The for loop just starts all your asynchronous operations and then you never know when they are all done unless you track them all somehow. That can be done without promises by using counters to keep track of when every single asynchronous result is done, but it's a whole lot easier to just let Promise.all() do that for you. It will also put all the results in the right order for you too.
If you wanted to sequence the database operations and run them serially one at a time, you could do this:
router.get('/user-post/:id', checkJwt, async (req, res, next) => {
try {
let da = await db.userSchema.findOne({ _id: req.params.id }).populate('posts').exec();
let postsArray = [];
for (let post of da.posts) {
let result = await db.postSchema.find({ _id: post._id }).populate('comments').exec();
postsArray.push(result);
}
res.json(postsArray);
} catch (e) {
console.log(e);
res.sendStatus(500):
}
});
This second version runs only one database operation at a time, sequentially. It will put less peak load on the database, but likely be slower to finish.
You will notice that the use of promises and await makes the error handling much simpler too as all errors will propagate to the same try/catch where you can log the error and send an error response. Your original code did not have error handling on your DB calls.

Select a document within the db.collection operation

I'm new here on the site, I have a question regarding a function in nodejs that I am unable to resolve. The solution is very simple but it does not work for me.
I build a social network in React, and for that I use nodeJs.
I have one function that I try to fix, but it does not work.
In firebase I have a collection called match, and in it I have details of users, and for each user, appear the other participants who are his friends.
For example according to the picture:
I have a collection of Match, in it I have my users, for example backend4 is a user's name, and in it, I have details of all the users it is suitable for.
This is the function I wrote down, but there is a problem with it, that it goes through all the users, the three users that can be seen in the picture.
exports.getMatchHandleArray = (req, res) => {
let engineers = [];
db.collection("match").get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const engineerDetails = doc.data();
if (engineerDetails.handle === req.user.handle) {
engineerDetails.handle;
engineers.unshift(engineerDetails);
}
else {
engineerDetails.handle;
engineers.push(engineerDetails);
}
});
console.log(engineers.length);
res.json(engineers);
});
};
I want it to go through only one user I choose, for that I did something like this:
exports.getMatchHandleArray = (req, res) => {
let engineers = [];
db.collection(`/match/${req.user.handle}`).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const engineerDetails = doc.data();
if (engineerDetails.handle === req.user.handle) {
engineerDetails.handle;
engineers.unshift(engineerDetails);
}
else {
engineerDetails.handle;
engineers.push(engineerDetails);
}
});
console.log(engineers.length);
res.json(engineers);
});
};
I get a 500 error message on this, I understand why, because I can not make a selection within db.collection.
I think something like this should be used:
db.doc (/ match / $ {req.user.handle}) but I can not do that
exports.getMatchHandleArray = (req, res) => {
let userData = {};
db.doc(`/match/${req.user.handle}`)
.get()
.then((doc) => {
if (doc.exists) {
userData.match = doc.data();
}
return res.json(userData.match);
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.code });
});
};
i find the solution that's works for me

How to send multiple objects from node backend to .hbs

I'm currently trying to send 2 objects to the front .hbs front end. However I cant seem to work out how to do this because I'm using promises.
Currently, my thinking is i perform the sql query, the country and organisation name is extracted, and then each sent to a geocoding api, returned and then squashed together in the same promises. But i'm not sure how to extract these for the render function.
Node
//route for homepage
app.get('/', (req, res) => {
let sql = "SELECT org_name, country_name from places;
let query = conn.query(sql, (err, results) => {
if (err) throw err;
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Promise.all(promises).then((geoLoc, geoBus) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
});
});
Front end call
results1 = {{{results}}}
console.log(results1.length)
business1 = {{{businesses}}}
console.log(business1.length)
Wrap your geo.geocode into a Promise
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
Combine both calls to geo.geocode
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Call them
Promise.all(promises).then(([geoLoc, geoBus]) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
As MadWard's answer mentions, deconstructing the argument of the callback of Promise.all is necessary since everything will be in the first argument. Make sure you check out his post for more details
Something important to recall: you will never have more than one argument in a then() callback.
Now you may ask: in the case of Promise.all(), what is this value?
Well, it is an array with all the values from the promises it awaits, in the order in which they are called.
If you do:
Promise.all([
resolveVariable1, resolveVariable2, resolveVariable3
]).then((values) => {
})
values will be [variable1, variable2, variable3], the three variables that the promises resolve to.
Your case is, however, a bit more complicated. What is gonna be returned at the end is a 2-D array containing every entry. It is an array of length results.length, and each of its element has a length of 2. The first element is the result, and the second one is the business.
Here is your snippet:
Promise.all(promises)
.then((values) => {
let results = values.map(elmt => elmt[0]);
let businesses = values.map(elmt => elmt[1]);
res.render('layouts/layout', {
results: JSON.stringify(results),
businesses: JSON.stringify(businesses)
});
})

Async/await in Express with multiple MongoDB queries

I have a fairly straightforward CRUD app which renders the results of two queries onto one page. The problem that arose once I got this to "work" was that the page required a refresh in order to display the results. On first load, no results were displayed.
I came to figure out that this is a problem/symptom of Node's asynchronous nature. I've been trying to approach this problem by using async/await, and from hours of messing with things, I feel like I'm quite close to the solution, but it's just not working out - I still need a manual refresh to display/render the results on the .ejs page.
The code:
var entries = [];
var frontPageGoals = [];
app.get('/entries', async (req,res) => {
if (req.session.password) {
const entriesColl = await
db.collection('entries')
.find()
.sort({date: -1})
.toArray((err, result) => {
if (err) { console.log(err) }
else {
for (i=0; i<result.length; i++) {
entries[i] = result[i];
}
}
});
const goalsColl = await
db.collection('goals')
.find()
.toArray((err, result) => {
if (err) {console.log(err)}
else {
for (i=0; i<result.length; i++) {
frontPageGoals[i] = result[i];
}
}
});
res.render('index.ejs', {entries: entries, frontPageGoals: frontPageGoals});
}
else {
res.redirect('/');
}
});
Now, I can conceive of a few problems here, but honestly I'm just at my wits end trying to figure this out. For example, I'm sure it's problematic that the empty lists which will contain the results to be passed when the page renders are outside the actual async function. But after trying to move them a dozen different places within the async area... still no dice.
Any help would be hugely appreciated! This is basically the last big "thing" I need done for this app.
I'm not 100% sure about your database driver, but assuming that the toArray() returns a promise (which it does in the default mongodb driver), the await will actually return the value you expect in your callback, result in your case, or in case there was an error, which you expected it as err in your callback, it will be thrown, thus forcing you to use try-catch blocks, in your case, you would just use console.log(err) in the catch block, since you aren't doing any handling
Here's your code after updating :
app.get("/entries", async (req, res) => {
if (req.session.password) {
try {
const entries = await db
.collection("entries")
.find()
.sort({ date: -1 })
.toArray();
const frontPageGoals = await db
.collection("goals")
.find()
.toArray();
res.render("index.ejs", {
entries: entries,
frontPageGoals: frontPageGoals
});
} catch (err) {
console.log(err);
}
} else {
res.redirect("/");
}
});
EDIT
However, if you don't know about promises -which async/await are basically promises-, and wanna just do it using callbacks -not advised-, you would have to just send your response in the callback, and nest the 2nd query in the first query's callback, here is the code,, with some comments to hopefully help you out:
app.get("/entries", (req, res) => {
if (req.session.password) {
// First query
db.collection("entries")
.find()
.sort({ date: -1 })
.toArray((err, entryResult) => {
if (err) {
console.log(err);
} else {
// In the callback of the first query, so it will
// execute 2nd query, only when the first one is done
db.collection("goals")
.find()
.toArray((err, frontPageResult) => {
if (err) {
console.log(err);
} else {
// In the callback of the 2nd query, send the response
// here since both data are at hand
res.render("index.ejs", {
entries: entryResult,
frontPageGoals: frontPageResult
});
}
});
}
});
} else {
res.redirect("/");
}
});
I have removed the async keyword since you no longer need it
I renamed the callback arguments, instead of just result, because both callbacks would have the same argument name, and you would have had to store it in a temp variable

KnexJS loop of queries

I'm very new with Node, KnexJS and promises and I'm trying to build a simple loop that queries items and then adds the pictures associated with them.
I looked at this answer and while it teaches a few things I don't think it works for my case: Knex Transaction with Promises
So far I have this:
router.get('/', function(req, res, next) {
knex('parts').where(req.query)
.orderBy('date_updated', 'DESC')
.then(function(data){
for (var k in data) {
knex('photos')
.select()
.where('part_id', data[k].id)
.then(function(photos){
data[k].photos = photos;
});
}
return data;
})
.then(function(data){
res.render('parts/index', { title: 'Express', data: data, query: req.query });
});
});
Which is obviously wrong, but I just don't know the approach in these cases.
Adding to IvanSF's answer, you can simply wrap a Promise.all() around that, to then res.send() the response. Like so:
Promise.all(rows.map(row => {
return knex('table')
.select('*').where('row_id', row.id)
.then(table => {
row.table = table;
return row;
});
})).then(response => {
res.send(response);
});
I used .map to get the desired effect.
.map(function(row) {
return knex('photos')
.select()
.where('part_id', row.id)
.then(function(photos) {
row.photos = photos;
return row;
});
})

Resources