Mongoose Async find all and update for each - javascript

I have little tipping game. After a game is done I get all tips from the mongoose db and then I iterate over these tips with forEach.
For each of these tips I get the username and load the user from the mongoose db to increase the points of this user and after that save the user changes back to the db.
One user can have more than one tip.
Tips.find({...}).exec(function(err, gameTips) {
gameTips.forEach(tip, i) => {
User.findOne({
username: tip.username
}).exec(function(err, user) {
user.points = user.points + 1;
user.save(function(err) {
console.log("Points were increased");
});
});
});
}
Now my problem is that the findOne of the user is done before the save of the prev tip processing. So the points will not be increased correctly.
User: testUser has 4 tips |
Expected: testUser.points = 4; |
Current: testUser.points = 2;
Is there a possibility to do that asynchronously so that find and save for all users will be done one after another so that each time I do:
user.points = user.points +1;
I will have the updated points before increasing?
EDIT
Thanks for your hints. I've tried to adopt that and my code is now:
async function updateUserPoints(schedule) {
try {
console.log("Load Schedules");
const scheduleTips = await Tip.find({
scheduleId: schedule._id,
season: schedule.season
});
console.log(scheduleTips);
if (scheduleTips.length) {
for (const scheduleTip of scheduleTips) {
console.log("Load User for scheduleTip: " + scheduleTip.tip);
let user = await User.findOne({
username: scheduleTip.username
})
console.log(user);
if (user) {
const winner = calculateWinner(schedule);
const points = calculatePoints(scheduleTip, winner);
console.log("WINNER: " + winner);
console.log("POINTS: " + points);
user.tippspiel.overallPoints = user.tippspiel.overallPoints + points;
user.tippspiel.seasonPoints = user.tippspiel.seasonPoints + points;
user.tippspiel.gameWeekPoints = user.tippspiel.gameWeekPoints + points;
await user.update({ username: scheduleTip.username }, { $inc: { "tippspiel.overallPoints": points } }, function(err) {
if (err) {
logger.error("[Tippspiel-User]: " + err);
} else {
logger.info("[Tippspiel-User]: User tippspiel points were updated.");
}
});
}
}
}
} catch (err) {
console.error(err);
}
}
function calculateWinner(schedule) {
let winner;
if (schedule.homeScore > schedule.awayScore) {
//Home wins
winner = "home";
} else if (schedule.homeScore < schedule.awayScore) {
//Away wins
winner = "away";
} else if (schedule.homeScore == schedule.awayScore) {
//Tie/Draw
winner = "draw";
}
return winner;
}
function calculatePoints(scheduleTip, winner) {
const POINTS_CORRECT_WINNER = settings.tippspiel.pointsCorrectWinner;
const POINTS_CORRECT_DRAW = settings.tippspiel.pointsCorrectDraw;
//If user has tipped correct
if (scheduleTip.tip === winner) {
let points = 0;
if ((scheduleTip.tip === "home") || (scheduleTip.tip === "away")) {
points = points + POINTS_CORRECT_WINNER;
} else if (scheduleTip.tip === "draw") {
points = points + POINTS_CORRECT_DRAW;
}
return points;
} else {
return 0;
}
}
I will test it now :)

You can't use async code the way you are using it in forEach, it will not yield the desired results. You can use for of with async await for a cleaner code:
async function updateTips() {
try {
const tips = await Tips.find({condition: 'condition'})
if (tips.length) { // check for empty result
for (const tip of tips) {
let user = await User.findOne({ username: tip.username })
if (user) {
user.points = user.points + 1
await user.save()
console.log('Points were increased')
}
}
}
} catch (err) {
// handle errors here
}
}
updateTips()

What happens is that you use the previous points to calculate the next score, instead use the mongoDB $inc operator
Option 1 using callbacks, ugly and not readable at all
Tips.find({})
.exec(function(err, gameTips) {
if(err) {
console.error(err);
return;
}
gameTips.forEach(tip => {
User.findOneAndUpdate(
{ username: tip.username },
{ $inc: { points: tip.points }}
).exec(function(err, user) {
if(err) {
console.error(err);
return;
}
console.log("Points were increased");
})
})
})
Option 2 using Promises, alot more readable with Promise.all()
Tips.find({})
.then(gameTips => Promise.all(gameTips.map(tip => User.updateOne(
{ username: tip.username},
{ $inc: { points: tip.points } }
)))
.then(() => {
console.log("Points were increased");
})
.catch(console.error)
Option 3 using async / await, my favorite, simple and readable
async function run() {
try {
const gameTips = await Tips.find({});
await Promise.all(gameTips.map(tip => User.updateOne(
{ username: tip.username},
{ $inc: { points: tip.points } }
)));
console.log("Points were increased");
} catch (err) {
console.error(err);
}
}

Related

Why do I occasionally get a 502 bad gateway error in my Express REST API?

I am currently testing out REST API stuff using Express... It works and stuff, but when I keep calling requests and tweaking variables between each request, every 3 or 4 times it returns a 502 bad gateway. I am unsure why. I'll share my code although note that its just WIP code im using to test stuff out.
Here is the code.
const express = require('express');
const bcrypt = require('bcrypt');
const router = express.Router();
// Models
const User = require('../models/User');
// User Class
class UserEntity {
// Initialize user
constructor(username, password=false) {
this.username = username;
this.password = password;
}
// Get Display Name
async getDisplayName() {
try {
const displayCheck = await User.findOne({username: this.username})
if (!displayCheck) {
return false;
}
return displayCheck.displayName;
} catch (err) {
console.log("Error while getting display name: " + err);
return false;
}
}
async login() {
// 0 = Success
// 1 = User not found
// 2 = Incorrect password
// 3 = Error or class missing password, check console
if (this.password != false) {
// Find user
try {
const userCheck = await User.findOne({ username: this.username });
if (!userCheck) {
return 1;
}
} catch (err) {
console.log("Error while checking if username exists: " + err);
return 3;
}
// Check password
try {
const userCheck = await User.findOne({username: this.username})
if (!userCheck) {
return 2;
} else {
const validPass = await bcrypt.compare(this.password, userCheck.password);
if (!validPass) {
return 2;
}
// logged in!
return 0;
}
} catch (err) {
console.log("Error while checking password: " + err);
return 3;
}
} else {
console.log("Class missing password");
return 3;
}
}
}
router.get("/test", async (req, res) => {
const user = new UserEntity("nero", "iamcool4");
const login = await user.login();
if (login == 0) {
res.send("Logged in!");
} else if (login == 1) {
res.send("User not found");
} else if (login == 2) {
res.send("Incorrect password");
} else if (login == 3) {
res.send("Error");
}
})

Mongoose 'Query was already executed' error

As the name states, I keep getting a "Query was already executed" while running Mongoose.find queries. Using '.clone' does not seem to be fixing the issue...
My calling code is:
let result = mongo.isValidUsername(req.body.username).then((data) => {
return data;
});
if ((await result) == false) {
res.send("Sorry, that username is unavailable");
} else {
mongo
.addUser(
req.body.username,
req.body.password,
req.body.firstName,
req.body.lastName,
req.body.email,
req.body.phoneNumber
)
.then(() => {
let profileData = mongo.getProfileData(req.body.username);
profileData
.then((data) => {
res.render("accountDisplay", {
results: data,
trans: [9.99],
});
})
.catch((err) => {
console.log(err);
});
});
}
I call a query twice - Once in isValidUsername() at the beginning (where I have not used .clone) and then again in getProfileData( where I HAVE used .clone).
I keep getting this error. Any idea what could be causing it?
Here is the code for isValidUsername() and getProfileData(), just in case...
async function isValidUsername(usernameToQuery) {
//connect to mongoose database
mongoose.connect("mongodb://localhost:27017/bankDB");
try {
let isValid = UserModel.findOne({ username: usernameToQuery }).then(
(data) => {
if (data == null) {
return true;
} else {
return false;
}
}
);
return await isValid;
} catch (err) {
return err;
}
}
async function getProfileData(usernameToQuery) {
mongoose.connect("mongodb://localhost:27017/bankDB");
let profileData = UserModel.findOne({ username: usernameToQuery }).clone();
console.log(await profileData);
let profileArray = await profileData.then((data) => {
return [
data._doc.firstName,
data._doc.lastName,
data._doc.email,
data._doc.phoneNumber,
];
});
return await profileArray;
}

Error when trying to pass value in async function (Stripe charging process)

I am trying to integrate Stipe into my project. In order to create a charge i need to create some values and pass it on to the next function. I currently have a async function chain in order to do so, but for some reason one of the values (created token) is not being passed on (error: undefined).
I tried it with passing the returned value of the function as well as saving the needed value in a object and then passing on the object.
Both customer and token are being correctly generated, but only the customer is being passed on to the addCardToCustomer function.
Can anyone spot a mistake a made here?
router.post("/checkout", async function (req, res, next) {
if (!req.session.cart) {
return res.redirect("/shopping-cart");
}
let cart = new Cart(req.session.cart);
let customerId = {};
let createCustomer = async function () {
var param = {};
param.email = req.body.email;
param.name = req.body.name;
param.description = "";
return stripe.customers.create(param, function (err, customer) {
if (err) {
console.log("err:" + err);
}
if (customer) {
console.log("success: " + JSON.stringify(customer, null, 2));
customerId.id = customer.id;
} else {
console.log("something went wrong");
}
});
//CUSTOMER CREATED
};
let createToken = async function () {
let param = {};
param.card = {
number: req.body.card,
exp_month: req.body.exp_month,
exp_year: req.body.exp_year,
cvc: req.body.security
}
return stripe.tokens.create(param, function (err, token) {
if (err) {
console.log("err:" + err);
}
if (token) {
console.log("success: " + JSON.stringify(token, null, 2));
console.log(req.body);
customerId.t_Id = token.id;
console.log(customerId.t_Id)
} else {
console.log("something went wrong");
}
});
};
//TOKEN CREATED
let addCardToCustomer = async function (createdToken) {
return stripe.customers.createSource(customerId.id, {
source: createdToken
}, function (err, card) {
if (err) {
console.log("err:" + err);
console.log(customerId.id)
//CUSTOMER IS DEFINED
console.log(customerId.t_id);
//TOKEN UNDEFINED
}
if (card) {
console.log("success: " + JSON.stringify(card, null, 2));
} else {
console.log("something went wrong");
}
});
};
//CUSTOMER.ID WORKS; TOKEN ID NOT
let chargeCustomerThroughCustomerID = async function () {
let param = {
amount: cart.totalPrice * 100,
currency: 'eur',
description: 'First payment',
customer: customerId.id
}
return stripe.charges.create(param, function (err, charge) {
if (err) {
console.log("err: " + err);
}
if (charge) {
console.log("success: " + JSON.stringify(charge, null, 2));
} else {
console.log("Something wrong")
}
})
}
try {
const createdCustomer = await createCustomer();
const createdToken = await createToken();
const addedCardToCustomer = await addCardToCustomer(createdToken);
const chargedCustomerThroughCustomerID = await chargeCustomerThroughCustomerID();
res.send("success");
} catch (e) {
console.log(`error ${e}`)
};
});
I already answered you on this question: your function are missing async and arguments.
See answer here: Stripe.create functions not in correct order

Alternative to complex else if ladders

I have a function that returns a value called user_id. But there are many conditions to be checked.
condition 1: check service variable
condition 2: If no, get user_id from localstorage
condition 3: If no, get firebase_uid from localstorage, and call function findUserId(firebase_uid) which will return user_id
condition 4: if no, get uid from firebase and call findUserId(uid)
here is the code.
export class UserService {
user_id : any;
firebase_uid: any;
id: any;
returnUser_id() {
if(this.user_id) {
return this.user_id
}
else {
this.storageService.get('user_id').then(val =>{
if(val) {
this.user_id =val
return this.user_id
}
else {
this.storageService.get('firebase_uid').then(value =>{
if(value) {
this.firebase_uid = value
this.findUserId(this.firebase_uid).subscribe(q =>{
console.log(q)
this.id = q;
for(let i =0; i<this.id.length;i++) {
this.user_id = this.id[i].id
return this.user_id
}
this.storageService.set('user_id', this.user_id ).then(result => {
console.log('Data is saved');
}).catch(e => {
console.log("error: " + e);
});
})
}
else {
this.afauth.authState.subscribe(user =>{
if(user) {
this.firebase_uid = user.uid;
this.storageService.set('firebase_uid', this.firebase_uid ).then(result => {
console.log('Data is saved');
}).catch(e => {
console.log("error: " + e);
});
this.findUserId(this.firebase_uid).subscribe(data =>{
this.id = data;
for(let i = 0 ;i<this.id.length; i++ ){
this.user_id = this.id[i].id
return this.user_id
}
this.storageService.set('user_id', this.user_id ).then(result => {
console.log('Data is saved');
}).catch(e => {
console.log("error: " + e);
});
})
}
})
}
}).catch(err =>{
console.log(err)
})
}
}).catch(err =>{
console.log(err)
})
}
}
}
findUserId function
findUserId(uid): Observable<any> {
return this.http.get<User[]>(this.user_url + 'users?filter[fields][id]=true&filter[where][firebase_uid]=' +uid )
}
This code is so complex and difficult to understand. Is there any alternative to traditional if else statements.
Thank you in advance
There are some repeatable code, so we can move repeatable code into method and reuse it. Moreover we can simplify our code using async keywords.
if(this.user_id)
return this.user_id;
else {
let user_id = await this.storageService.get('user_id');
if (user_id)
return user_id;
else {
let firebase_uid = await this.storageService.get('firebase_uid');
if (firebase_uid) {
await reusableFindUserId(firebase_uid);
if (this.user_id)
await setStorageService();
}
else {
let user = await this.afauth.authState();
if (user) {
this.firebase_uid = user.uid;
await reusableFindUserId(firebase_uid);
if (this.user_id)
await setStorageService();
}
})
}
}
}
and reusable methods:
async reusableFindUserId(firebase_uid){
this.id = await this.findUserId(firebase_uid);
for(let i =0; i<this.id.length;i++) {
this.user_id = this.id[i].id;
return this.user_id;
}
}
async setStorageService() {
return await this.storageService.set('user_id', this.user_id );
}
You can get rid of this else, because you return in the if-block above. If you don't return, the remaining code will be executed.
You can go through the hole function and check if the elses are necessary. If you return, you don't need an else :)
Another point is, you can extract some parts of the code into dedicated functions. Your main function will be much cleaner and shorter.
if(val) {
this.user_id =val
return this.user_id
}
// No need for the else...
else {
....
}

how to handle expressJs callback and how to update object's property inside a function?

I have two js files. i am able to get data from mongodb by calliing bookDao.getActiveBookByCategoryId().
My Problem
In categoryDao.js file i am trying to update resultJson.book_countinside BookDao.getActiveBookByCategoryId() method. but it is not updating. So may i know how to fix this.
here book_count property in resultJson is still 0.
categoryDao.js
module.exports.getAllActiveCategory = (callback) => {
Category.find({
is_delete : false
}, (error, result) => {
if(error) {
console.log(error);
callback(commonUtil.ERROR);
}
if(result) {
var categoryArray = [];
for(var i=0; i<result.length; i++) {
var categorySingle = result[i];
var resultJson = {
_id : categorySingle._id,
category_name : categorySingle.category_name,
created_on : categorySingle.created_on,
book_count : 0
}
BookDao.getActiveBookByCategoryId(categorySingle._id, (bookResult) => {
if(bookResult) {
if(bookResult.length > 0) {
resultJson.book_count = bookResult.length;
}
}
});
categoryArray.push(resultJson);
}
callback(categoryArray);
}
});
}
bookDao.js
module.exports.getActiveBookByCategoryId = (categoryId, callback) => {
Book.find({
is_delete : false,
category : categoryId
}, (error, result) => {
if(error) {
console.log(error);
callback(commonUtil.ERROR);
}
if(result) {
callback(result);
}
});
}
Try this, In your code categoryArray.push(resultJson); will not wait for BookDao.getActiveBookByCategoryId to finish because of async behavior.
module.exports.getActiveBookByCategoryId = (categoryId) => {
return Book.count({
is_delete: false,
category: categoryId
});
}
module.exports.getAllActiveCategory = async () => {
try {
// Find all category
const result = await Category.find({
is_delete: false
});
// Create array of promise
const promises = result.map(categorySingle => BookDao.getActiveBookByCategoryId(categorySingle._id));
// Get array of Category count
const data = await Promise.all(promises);
// update count in result
return result.map((categorySingle, i) => {
categorySingle.book_count = data[i];
return categorySingle;
});
} catch (error) {
console.log(error);
}
}

Categories