How to deal with race conditions in nodejs - javascript

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,
});

Related

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

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 })
}
})

Is there a way to access my response error message?

Ok So i am trying to display my backend error messages in the front end, so I have it setup to send the response with the error code and a message and then in my action I am setting a state in my React component which I will then use to display the error message, so far I can get to display the error code but that is no use to most users so I would like to access the message I send with the code! So I want it to say user already exists or passwords do not match rather than Error: Request failed with status code 400
my action
export const signup = (form, router, setError) => async (dispatch) => {
const changeError = (error) => {
setError(error);
};
try {
const { data } = await api.signup(form);
dispatch({ type: AUTH, data });
router.push("/");
} catch (error) {
console.log(error);
changeError(error);
}
};
my node signup
export const signup = async (req, res) => {
const { email, password, confirmPassword, firstName, lastName } = req.body;
try {
const existingUser = await user.findOne({ email });
if (existingUser)
return res.status(400).json({ message: "User already exists." });
if (password != confirmPassword)
return res.status(400).json({ message: "Passwords do not match." });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await user.create({
email,
password: hashedPassword,
name: `${firstName} ${lastName}`,
});
const token = jwt.sign(
{ email: result.email, id: result._id },
process.env.JWT_KEY,
{
expiresIn: "1h",
}
);
res.status(200).json({ result, token });
} catch (error) {
res.status(500).json({ message: "Something went wrong." });
}
};
After little search on Google, if you are using Axios as your api, the path to the error message is:
error.response.data.message
else, have you tried somthing like this?
error.data.message
or
error.message
as Guy said, slightly before I found the answer myself I set the error to error.response.data.message
so now I can set my error in the front end to display the message
and yea sorry was using axios, I'll know better for next time to mention that!
export const signup = (form, router, setError) => async (dispatch) => {
const changeError = (error) => {
setError(error);
};
try {
const { data } = await api.signup(form);
dispatch({ type: AUTH, data });
router.push("/");
} catch (error) {
console.log(error);
changeError(error.response.data.message);
}
};

Not able to call promise that is returned from within a function

I have a function in the middleware file called setSignedInUser which receives a cookie as a parameter. I want to find a user saved in the SignedIn collection with the cookie,retrieve the user's unique id ,search for the user's complete information from the Users collection and return the foundUser which I would like to access in another file where the function is called from.
The middleware file:
const { resolve } = require('path');
const User = require('./Models/User'),
SignedIn = require('./Models/SignedIn');
module.exports = {
setSignedInUser : (cookie)=>{
console.log(cookie)
SignedIn.findOne({secretToken : cookie})
.then(foundSignIn=>{
userId = foundSignIn.userId;
User.findOne({_id : userId})
.then(foundUser=>{
console.log(foundUser) // User is successfully found and loged to the console
return new Promise((resolve , reject)=>{
if(foundUser){
resolve(foundUser);
}else{
reject('error')
}
})
})
.catch(err=>{
console.log(err)
})
})
.catch(err=>{
console.log(err)
})
}
}
I call this function from the index.js file where my route is.
The index.js file:
const middleware = require('../middleware');
router.get('/' , (req , res)=>{
middleware.setSignedInUser(req.cookies.auth_cook)
.then(foundUser=>{
console.log(foundUser)
})
.catch(err=>{
console.log(err)
})
res.render('../views/general/home.ejs' , {user : user})
});
But here I get an error saying Cannot call .then on undefined
What am I missing here? Thankyou
You are not returning anything from your setSignedInUser function, so foundUser is undefined. A correct way would be this:
module.exports = {
setSignedInUser: (cookie) => {
return new Promise((resolve, reject) => {
SignedIn.findOne({
secretToken: cookie
})
.then((foundSignIn) => {
userId = foundSignIn.userId;
User.findOne({
_id: userId
})
.then((foundUser) => {
if (foundUser) {
resolve(foundUser);
} else {
reject('error');
}
})
.catch((err) => {
console.log(err);
reject(err);
});
})
.catch((err) => {
console.log(err);
reject(err);
});
});
},
};
Since you want the result of an inner function you can return a Promise and resolve it with the inner value.
I think it's because you return the new promise inside your 2nd level promise.
Try to pass User.findOne in async like this:
module.exports = {
setSignedInUser : (cookie)=>{
SignedIn.findOne({secretToken : cookie})
.then(async foundSignIn=>{
userId = foundSignIn.userId;
const foundUser = await User.findOne({_id : userId})
if(foundUser){
return foundUser;
} else {
return Promise.reject("error"}
}
})
.catch(err=>{
console.log(err)
})
}
}
I'm not sure but I think you should try this,
try changing declaration of function like this. :
Just replace : with =
setSignedInUser = (cookie)=>{
}

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' })
}
})

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