Alternative to complex else if ladders - javascript

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 {
....
}

Related

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

Mongoose Async find all and update for each

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

wait for asynchronous functions to finish in Angular

So I was working on a new component in Angular and in the ngOninit I have the following asynchronous functions below...
This.getUserProfile needs to be finished before I can call this.getPrivateGroup() and this.getPrivateGroup() needs to be finished before I can call this.loadGroupPosts(). I know I could write these functions inside the callback of the asynchronous requests, but I was wondering if there is a way to keep it in ngOnInit to keep it cleaner?
Anyone has an idea?
ngOnInit() {
this.getUserProfile();
// my-workplace depends on a private group and we need to fetch that group and edit
// the group data before we proceed and get the group post
if (this.isItMyWorkplace) {
this.getPrivateGroup();
}
this.loadGroupPosts();
}
getUserProfile() {
this._userService.getUser()
.subscribe((res) => {
this.user = res.user;
console.log('log user', this.user);
this.profileImage = res.user['profile_pic'];
this.profileImage = this.BASE_URL + `/uploads/${this.profileImage}`;
}, (err) => {
this.alert.class = 'alert alert-danger';
if (err.status === 401) {
this.alert.message = err.error.message;
setTimeout(() => {
localStorage.clear();
this._router.navigate(['']);
}, 3000);
} else if (err.status) {
this.alert.class = err.error.message;
} else {
this.alert.message = 'Error! either server is down or no internet connection';
}
});
}
getPrivateGroup() {
console.log('user check', this.user);
this.groupService.getPrivateGroup(`${this.user.first_name}${this.user.last_name}`)
.subscribe((group) => {
console.log('received response', group)
})
}
// !--LOAD ALL THE GROUP POSTS ON INIT--! //
loadGroupPosts() {
this.isLoading$.next(true);
this.postService.getGroupPosts(this.group_id)
.subscribe((res) => {
// console.log('Group posts:', res);
this.posts = res['posts'];
console.log('Group posts:', this.posts);
this.isLoading$.next(false);
this.show_new_posts_badge = 0;
}, (err) => {
swal("Error!", "Error while retrieving the posts " + err, "danger");
});
}
// !--LOAD ALL THE GROUP POSTS ON INIT--! //
You can use basic promises with async/await.
async ngOnInit() {
await this.getUserProfile(); // <-- 1. change
// my-workplace depends on a private group and we need to fetch that group and edit
// the group data before we proceed and get the group post
if (this.isItMyWorkplace) {
this.getPrivateGroup();
}
this.loadGroupPosts();
}
async getUserProfile() {
this._userService.getUser()
.subscribe((res) => {
this.user = res.user;
console.log('log user', this.user);
this.profileImage = res.user['profile_pic'];
this.profileImage = this.BASE_URL + `/uploads/${this.profileImage}`;
return true; // <-- this
}, (err) => {
this.alert.class = 'alert alert-danger';
if (err.status === 401) {
this.alert.message = err.error.message;
setTimeout(() => {
localStorage.clear();
this._router.navigate(['']);
}, 3000);
} else if (err.status) {
this.alert.class = err.error.message;
} else {
this.alert.message = 'Error! either server is down or no internet connection';
}
throw err;
});
}
You could instead leverage RxJS and use a switchMap something like this (syntax NOT checked):
getData(): Observable<string[]> {
return this._userService.getUser()
.pipe(
switchMap(userInfo=> {
return this.getPrivateGroup();
}),
catchError(this.someErrorHandler)
);
}
One way to do is, return the Observable instead of subscribing in the getPrivateGroup()
getPrivateGroup() {
console.log('user check', this.user);
return this.groupService.getPrivateGroup(`${this.user.first_name}${this.user.last_name}`)
}
And then, subscribe to the data where you want the chain the this.loadGroupPosts()
if (this.isItMyWorkplace) {
this.getPrivateGroup().subscribe(group => {
this.group = group; //you probably want to assign the group data
this.loadGroupPosts()});
}
you could also use the 3rd part of your subscribe function when its completed
i am not quite sure if this is a clean solution, in my opinion it is.
ngOnInit() {
this.getUserProfile();
}
getUserProfile() {
this._userService.getUser()
.subscribe((res) => {
this.user = res.user;
console.log('log user', this.user);
this.profileImage = res.user['profile_pic'];
this.profileImage = this.BASE_URL + `/uploads/${this.profileImage}`;
}, (err) => {
this.alert.class = 'alert alert-danger';
if (err.status === 401) {
this.alert.message = err.error.message;
setTimeout(() => {
localStorage.clear();
this._router.navigate(['']);
}, 3000);
} else if (err.status) {
this.alert.class = err.error.message;
} else {
this.alert.message = 'Error! either server is down or no internet connection';
}
}, () => {
// my-workplace depends on a private group and we need to fetch that group and edit
// the group data before we proceed and get the group post
if (this.isItMyWorkplace) {
this.getPrivateGroup();
}
});
}
getPrivateGroup() {
console.log('user check', this.user);
this.groupService.getPrivateGroup(`${this.user.first_name}${this.user.last_name}`)
.subscribe((group) => {
console.log('received response', group)
}, error => {
console.log(error)
}, () => {
this.loadGroupPosts();
})
}
loadGroupPosts() {
this.isLoading$.next(true);
this.postService.getGroupPosts(this.group_id)
.subscribe((res) => {
// console.log('Group posts:', res);
this.posts = res['posts'];
console.log('Group posts:', this.posts);
this.isLoading$.next(false);
this.show_new_posts_badge = 0;
}, (err) => {
swal("Error!", "Error while retrieving the posts " + err, "danger");
});
}

how to let a function wait for result returned by another function?

i have a function called 'updateProfile()' which has a condition which is if(emailChangeConfirm), this condition depends upon the value of variable 'emailChangeConfirm' , this variable gets the value returned by another function called 'updateEmailAllProcessing()'
the condition 'if(emailChangeConfirm)' is not getting satisfied at all because compiler is not waiting for the function 'updateEmailAllProcessing()' to return the value for variable 'emailChangeConfirm'.
I used async/await for this but that is also not working as i want
Desired Solution :
function 'updateProfile()' must wait for the function 'updateEmailAllProcessing()' to get the result in 'emailChangeConfirm' so that i can enter in the condition 'if(emailChangeConfirm)'.
I am using typescript and working on hybrid app with ionic 3 and angular 5.
async updateProfile(updatedData : Credentials,tarUser : Credentials)
{
// console.log(tarUser,'<<--->>>',updatedData)
let count : number = undefined;
let emailChangeConfirm : boolean;
if(updatedData.name)
{
if(tarUser.name != updatedData.name)
tarUser.name = updatedData.name;
else
count++;
}
if(updatedData.email)
{
if(tarUser.email != updatedData.email)
{
**emailChangeConfirm = await this.updateEmailAllProcessing();**
console.log(emailChangeConfirm)
**if(emailChangeConfirm)
tarUser.email = updatedData.email;**
}
else
count++;
}
if(updatedData.phoneNo)
{
if(tarUser.phoneNo != updatedData.phoneNo)
tarUser.phoneNo = updatedData.phoneNo;
else
count++;
}
if(updatedData.photoURL)
{
if(tarUser.photoURL != updatedData.photoURL)
tarUser.photoURL = updatedData.photoURL;
else
count++;
}
if(count)
this.mesService.presentToast('Nothing Updated!!')
else **if(emailChangeConfirm)**
{
this.dbServe.editUser(tarUser).then(() =>
{
console.log("User Edited Successfully with email change too");
this.authServ.updateEmail(tarUser.email).then(() =>
{
console.log('login email updated');
this.authServ.logout();
})
//this.close();
})
}
else
{
this.dbServe.editUser(tarUser).then(() =>
{
console.log("User Edited Successfully with No email change");
this.close();
})
}
}
**async updateEmailAllProcessing()**
{
let result : boolean;
let alert = this.mesService.emailChangeConfirmation();
alert.present();
alert.onDidDismiss((data) => {
console.log('data->',data);
if(data)
{
let alert1 = this.mesService.passwordPrompt();
alert1.present();
alert1.onDidDismiss(data1 =>
{
console.log('password->',data1);
if(data1)
{
this.authServ.reauthenticateUser(data1).then(() =>
{
console.log('User Reauth Done');
result = true;
})
}
else
result = false;
})
}
else
result = false;
})
**return result;**
}
You need updateEmailAllProcessing to return a promise so you can await on it. and resolve the promise with the result inside the callback.
async updateEmailAllProcessing()
{
return new Promise((resolve, reject) => {
let result: boolean;
let alert = this.mesService.emailChangeConfirmation();
alert.present();
alert.onDidDismiss((data) => {
console.log('data->', data);
if (data) {
let alert1 = this.mesService.passwordPrompt();
alert1.present();
alert1.onDidDismiss(data1 => {
console.log('password->', data1);
if (data1) {
this.authServ.reauthenticateUser(data1).then(() => {
console.log('User Reauth Done');
resolve(true);
})
}
else
resolve(false);
})
}
else
resolve(false);
})
});
}

Angular 5 nested returns

i am trying to create a function that should return a boolean after calling my API
so i created the following function:
verify() {
console.log('lol');
let user = JSON.parse(localStorage.getItem('currentUser')) as User;
return this.http.get(environment.apiUrl + 'Users/' + user.id,
{params: ApiHelperService.prototype.createParams([], true)}).subscribe(
result => {
return true;
},
error => {
return false;
}
);
}
And then i called it:
if (this._userService.verify()) {
return true;
}
else {
this._router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
return false;
}
However, sadly the function returns before the response from the server.
So my question is how can I create these nested return statements to make sure it returns the correct value?
suggest you make use of async/await way like as below
async verify() {
console.log('lol');
let user = JSON.parse(localStorage.getItem('currentUser')) as User;
return await this.http.get(environment.apiUrl + 'Users/' + user.id,
{params: ApiHelperService.prototype.createParams([], true)})
).toPromise();
}
use it like this
async verifyoutput()
{
try
{
const verify = await verify();
return true;
}
catch (e)
{
return false;
}
}
verify():Observable<any> {
console.log('lol');
let user = JSON.parse(localStorage.getItem('currentUser')) as User;
return this.http.get(environment.apiUrl + 'Users/' + user.id,
{params: ApiHelperService.prototype.createParams([], true)})
}
...
verify().subscribe(result => {
return true
},err => {
this._router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
return false;
})

Categories