res.json() called before the for loop is fully executed - javascript

I've got an endpoint in my Application that is supposed to fetch data from a MongoDb Database and return the results to the Client. However, before my for(){} loop finishes execution and an empty array ends up getting sent. How do I change my Code where I can send the Array only when it's completely populated.
Code Below:
app.get("/check/classes/usercart", (req, res) => {
if(req.session.user){
UserCart.findOne({userId: req.session.user._id}).lean().exec()
.then((foundUserCart)=>{
if(foundUserCart){
console.log("Found user in the UserCart Collection", foundUserCart);
console.log("Printing User's classCart", foundUserCart.classCart);
//return res.json({classes: foundUserCart.classCart});
const classesArray = foundUserCart.classCart;
let arrayToSend = [];
for(let i = 0; i < classesArray.length; i++){
Classes.findById({_id: classesArray[i]}).lean().exec()
.then((foundClass)=>{
console.log("Class Found", foundClass);
arrayToSend.push(foundClass);
console.log("Printing ArrayToSend", arrayToSend);
})
.catch((error)=>{
console.log("Error performing findById", error);
return res.json({msg: "Server Error"});
});
}
//return arrayToSend;
//This get's executed before my for loop finishes. I'm new to NodeJs and
//the whole asynchronous cycle
return res.json({classes: arrayToSend});
}else{
return res.json({msg: "No records found for this user"});
}
})
.catch((error)=>{
console.log("Error performing UserCart.find() operation", error);
return res.json({msg: "Server Error"});
});
}else{
return res.redirect("/");
}
});
Please could somebody kindly give me some suggestions? Been stuck on this for a while now. Thanks.
Update:
So I got around this by doing the following:
Classes.find({'_id': {$in: classesArray}})
.then((foundRecords)=>{
console.log("Found records", foundRecords);
return res.json({classes: foundRecords});
}).
catch((error)=>{
console.log("Error here", error);
});
This just returns the entire array of records. Probably not the cleanest solution. Could somebody tell me how to do this entire function using Async Await?

You can make your query with multiple IDs in a single operation. That would eliminate the need for the for loop.
const listOfIds = ["46df4667tfs57", "477dfs884v73", "88er58366s"];
const arrayToSend = await Classes.find({ _id: { $in: listOfIds } }).lean().exec();

Using Promise.all
app.get("/check/classes/usercart", (req, res) => {
if (req.session.user) {
return UserCart.findOne({userId: req.session.user._id}).lean().exec()
.then(foundUserCart => {
if (foundUserCart) {
const { classCart } = foundUserCart;
const arrayToSend = classCart.map(_id => Classes.findById({_id}).lean().exec());
return Promise.all(arrayToSend)
.then(classes => res.json({classes}));
}
return res.json({msg: "No records found for this user"});
})
.catch((error)=>{
console.log("Error performing UserCart.find() operation", error);
return res.json({msg: "Server Error"});
});
}
res.redirect("/");
});
Bonus: Rewrite the above using Async/await
app.get("/check/classes/usercart", async (req, res) => {
try {
if (!req.session.user) {
return res.redirect("/");
}
const foundUserCart = await UserCart.findOne({userId: req.session.user._id}).lean().exec();
if (!foundUserCart) {
return res.json({msg: "No records found for this user"});
}
const { classCart } = foundUserCart;
const arrayToSend = classCart.map(_id => Classes.findById({_id}).lean().exec());
const classes = await Promise.all(arrayToSend);
res.json({classes});
} catch (error) {
res.json({msg: "Server Error"});
}
});

Use async/await to clean your code. Your final controller will look like this
app.get('/check/classes/usercart', async (req, res) => {
try {
if (!req.session.user) return res.redirect('/')
const userCart = await UserCart.findOne({ userId: req.session.user._id }).lean()
if(!userCart) throw new Error('User Not Found!')
const classes = await Classes.find({ _id: { $in: userCart.classCart } }).lean()
return res.json({ classes })
} catch (error) {
console.log('Error occured', error)
res.json({ message: 'Error Occured!', error })
}
})

Related

Issue while trying to call an existing route from within another route in Express JS project

In an Express JS connected to a mySQL db, I am trying to get some data of an already defined route/ query:
// customers.model.js
CUSTOMERS.getAll = (result) => {
let query = "SELECT * FROM customers"
sql.query(query, (err, res) => {
if (err) {
console.log("error: ", err)
result(null, err)
return
}
result(null, res)
})
}
// customers.controller.js
// GET customers is a standalone route and should output all the customers when called.
const CUSTOMERS = require("../models/customers.model.js")
exports.findAll = (req, res) => {
return CUSTOMERS.getAll((err, data) => {
if (err)
res.status(500).send({
message: err.message ||
"Some error occurred while retrieving customers...",
})
else res.send(data)
})
}
In payments.controller.js I would firstly like to get all users so I can do something with the data:
// payments.controller.js
// GET payments is also a standalone route and should get the customers,
// do something with the data and output a calculation with the help of this data
const CUSTOMERS = require("../models/customers.model.js")
exports.calculateAll = (req, res) => {
const customers = CUSTOMERS.getAll((err, data) => {
console.log('this always has correct data', data)
if (err) return err
else return data
})
console.log('this is always undefined', customers)
...
res.send(whatEverCalculatedData)...
}
But that data here is always undefined.
What am I doing wrong in the above, and what's the correct way to call this route inside another route?
I know it has similarities with this question but I couldn't sort it out for my particular example.
It's due to your call which is asynchronous.
You must wait your data being ready before rendering the results.
Maybe you could to use Promises or async/await statements.
For example:
CUSTOMERS.getAll = async () => {
const query = "SELECT * FROM customers";
try {
return await sql.query(query);
} catch (e) {
console.log(`An error occurred while fetching customers: ${e.message}.`);
return null;
}
}
exports.calculateAll = async (req, res) => {
try {
const data = await CUSTOMERS.getAll();
res.send(whatEverCalculatedData);
} catch (e) {
res.send(`Something went wront: ${e.message}.`);
}
}

Can't get data via filter () in mongodb

I am trying to find the tasks assigned to the user through this
router.get('/all', auth, async (req, res) => {
try {
const assignments_raw = await Assignment.find({listP: listP.indexOf(req.user.userId)})
res.json(assignments_raw)
} catch (e) {
res.status(500).json({ message: 'Something went wrong, try again' })
}
})
Specifically, this line should have found all the tasks that have an element corresponding to the user ID inside the listP field
const assignments_raw = await Assignment.find({listP: listP.indexOf(req.user.userId)})
But this causes an error, why?
below is an excerpt from Mango
You can do like this
router.get('/all', auth, async (req, res) => {
try {
const assignments_raw = await Assignment.find()
let assignments = []
assignments_raw.map(assignment => {
if (assignment.listP.indexOf(req.user.userId) !== -1) {
assignments.push(assignment)
}
}
)
res.json(assignments)
} catch (e) {
res.status(500).json({ message: 'Something went wrong, try again' })
}
})

Why do I get an unhandled promise error when i dont have a promise set?

I ran into an issue with an axios post request
const addOrder = (newOrder) => {
axios
.post("http://localhost:83/api/addorder", {
newRow: newOrder,
newRecID: orders[0].RecID + 1,
})
.then((response) => {
console.log(response);
getOrders();
})
.catch((err) => {
console.log("Err", err);
});
In my API the request handler is this:
app.post("/api/addorder", function (req, res) {
const newData = req.body.newOrder;
let newRecID = req.body.newRecID;
sql.connect(config, function (err) {
if (err) {
console.log(err);
return;
}
var request = new sql.Request();
request.query(
`INSERT INTO dbo.Einkauf_Web_Bestellungen_DEV (RecID) VALUES (${newRecID})`,
function (err, result) {
if (err) {
console.log(err);
}
}
);
});
});
The SQL Query is executed and succesfully inserts the given value. But my react application crashes and spits out following error:
Screenshot of the application console

How to deal with race conditions in nodejs

I'm trying to work on a MERN full stack app where the frontend sends an api call to "/createDeckOfCards" to my nodejs backend. The goal is to click on a button to create a new deck of cards, then return the list of created cards.
The parameter numOfCards is sent with this call as well.
So on my nodeJS backend, I have the "/createDeckOfCards" endpoint where I use .map() to iteratively create each card and then save to mongoDB like so:
const allCardsArray = [...Array(req.body.numOfCards).keys()]
allCardsArray.map(async (i)=>{
const eachCard = new eachCardModel({
eachCardTitle: String(i)
})
eachCard.save((err, doc) => {
if (err) return res.status(400).json({ errMsg: "Something went wrong" });
else{
CardDeckModel.findOneAndUpdate(
{_id: req.cardDeckCreated._id},
{$push:{allCards: doc}},
function(error, success){
if (error){
console.log(error)
return res.status(400).json({ errMsg: "Something went wrong" });
} else {
console.log("success")
}
}
)
}
});
})
console.log("COMPLETED") //DOES NOT EXECUTE LAST!
//THIS RETURNS BEFORE THE .map() is done
res.status(200).json({
createdCardDeckID: req.cardDeckCreated._id
})
})
After that, I have a second endpoint "/returnAllCardsInDeck" where I pass in the ID of the cardDeck like so:
CardDeckModel.findOne({_id: req.body.createdCardDeckID}).populate({path: 'allCards', options: { sort: "eachCardTitle" } }).exec((err, cardDeck) => {
if (err) return res.status(400).json({ errMsg: "Something went wrong" });
else {
res.status(200).json({
CardDeck: cardDeck
})
}
})
The problem is, CardDeck returns before the allCardsArray.map() is completed. This would be a problem because I want the user to see ALL cards in the deck once the deck is created. But because the "/returnAllCardsInDeck" executes before the "/createDeckOfCards", it returns be an undefined object.
Also, am I doing this right? Esp with regards to the first part ("/createDeckOfCards").
try this, you can't do async call like this with map. There are patterns to solve this issue. Promise.all is one of them.
const allCardsArray = [...Array(req.body.numOfCards).keys()]
await Promise.all(
allCardsArray.map((i)=>{
const eachCard = new eachCardModel({
eachCardTitle: String(i)
})
return eachCard.save()
.then(
() =>
CardDeckModel.findOneAndUpdate({_id: req.cardDeckCreated._id}, {$push:{allCards: doc}})
.then(() => console.log("success"))
.catch((error) => console.log(error) || res.status(400).json({ errMsg: "Something went wrong" });)
).catch(() => res.status(400).json({ errMsg: "Something went wrong" }))
})
)
console.log("COMPLETED") //DOES NOT EXECUTE LAST!
//THIS RETURNS BEFORE THE .map() is done
res.status(200).json({
createdCardDeckID: req.cardDeckCreated._id
})
})
you can use for of with async/await instead of map like this
const allCardsArray = [...Array(req.body.numOfCards).keys()];
for (let i of allCardsArray) {
const eachCard = new eachCardModel({
eachCardTitle: String(i),
});
try {
let doc = await eachCard.save();
await CardDeckModel.findOneAndUpdate(
{ _id: req.cardDeckCreated._id },
{ $push: { allCards: doc } }
);
} catch (error) {
return res.status(400).json({ errMsg: "Something went wrong" });
}
}
console.log("success");
res.status(200).json({
createdCardDeckID: req.cardDeckCreated._id,
});

RESTful API for HTTP DELETE doesnt check for null

Im currently writing a RESTful API for a webservice but im having trouble. Im trying to delete an mail, but first i want to check if the mail even exists. My problem is that it doesn't check if mail is null and doesn't respond with a 404. Im working with express and mongoose
router.delete('/:id', (req, res) => {
const { id } = req.params;
Mail.findById(id)
.exec()
.then((mail) => {
if (!mail) {
console.log(mail) // returns null
return res.status(404);
}
})
.then(
Mail.deleteOne({ _id: id })
.exec()
.then(() => {
res.status(200).json({
message: 'Mail deleted',
});
})
.catch((err) => {
res.status(500).json({ error: err });
})
);
});
I think you have to do the the deletion part of the code inside the first then block as an else statement. You are not returning anything that the next then block can use.
you could do:
Mail.findById(id)
.exec()
.then((mail) => {
if (!mail) {
console.log(mail) // returns null
return res.status(404).send() //need to send response;
}
Mail.deleteOne({ _id: id })
.exec()
.then(() => {
res.status(200).json({
message: 'Mail deleted',
});
})
}).catch((err) => {
res.status(500).json({ error: err });
})
PRO TIP: if you don't know it, learn async await. Code will look much cleaner!
Then it would look like this:
router.delete('/:id', async (req, res) => {
const { id } = req.params;
try {
const mail = await Mail.findById(id);
if(!mail) {
return res.status(404).send();
}
await Mail.deleteOne({_id: id});
res.status(200).json({
message: 'Mail deleted',
});
} catch(e) {
res.status(500).json({ error: err });
}

Categories