Is wrapping an ES6 promise within another an anti-pattern? - javascript

I've taken a look at Brad Traversy's User routes for DevConnector, a project he uses to teach Node.js. The code does not look very clean or self-explanatory enough in my opinion; take a look at the /register route for example - it's all written in one big block. I am wondering if wrapping promises within other promises can solve this issue.
Below is what my alternative looks like:
router.post('/register', (req, res) => {
const firstName = req.body.firstName;
const lastName = req.body.lastName;
const email = req.body.email;
const password = req.body.password;
const dateOfBirth = req.body.dateOfBirth;
buildUserIfNotExists(email, firstName, lastName, dateOfBirth)
.then(user => hashUserPassword(user, password, 10))
.then(user => saveUser(user))
.then(user => {
sendActivationLink(user);
res.json(user);
})
.catch(errors => {
if (errors.internalError) {
console.log(errors.internalError);
res.status(500).json({
internalError: 'An internal error occured.'
})
} else {
res.status(400).json(errors);
}
});
});
An example of promise wrapper as I see it would be:
function saveUser(user) {
const errors = {};
return new Promise((resolve, reject) => {
user
.save()
.then(user => resolve(user))
.catch(err => {
errors.internalError = err;
reject(errors);
})
});
}
So far I've got no issues with this approach, everything works as expected. Are there any downsides to this that I am missing? Is there any way to simplify this even further?

I am not very experienced with JavaScript but I found the following simplification; instead of:
.then(user => saveUser(user))
I can simply do:
.then(user => user.save())
Actually, after a few modifications, my code looks like this:
router.post('/register', (req, res) => {
const newUser = new User({
name: req.body.name,
hometown: req.body.hometown,
dateOfBirth: req.body.dateOfBirth,
email: req.body.email,
activationHash: nanoid()
});
ensureUserNotExists(newUser)
.then(() => hashUserPassword(newUser, req.body.password))
.then(() => newUser.save())
.then(() => {
sendActivationLink(newUser).then(() => res.json(newUser))
})
.catch(errors => {
if (errors.internalError) {
console.log(errors.internalError);
res.status(500).json({
internalError: 'An internal error occured.'
})
} else {
res.status(400).json(errors);
}
});
});

Related

firebase - Use updateProfile whenever a user signup

I have a problem with firebase, I want when a user creates a user for the first time, add him to updateProfile, personal details.
This is the code I'm trying to do but the code is not running, it does not work for me.
The part with the currentUser does not work, I do not understand why, I also do not get an error.
signupUser = async () => {
const newUser = {
email: 'test#mail.com',
password: '123456'
};
await signup(newUser);
}
call to signup in nodejs
export const signup = (newUser) => (dispatch) => {
axios
.post('/signup', newUser)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
signup - nodejs
//basically call to this function to signup
exports.signup = (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password
};
firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password)
.then((data) => {
const currentUser = firebase.auth().currentUser;
const name = `${"adding some private information"}`;
currentUser.updateProfile({
displayName: name,
})
.then(() => {
console.log("sign in successfully")
});
return data.user.getIdToken();
})
.then((token) => {
return db.doc(`/users/${newUser.handle}`).set("test");
})
.then(() => {
return res.status(201).json({ token });
})
.catch((err) => {
console.error(err);
});
};
The issue looks to be that you aren't return the promise from currentUser.updateProfile, ensuring it successfully completes. Try the following by returning the Promise from that method:
exports.signup = (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
};
firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password)
.then((data) => {
const currentUser = firebase.auth().currentUser;
const name = `${"adding some private information"}`;
return currentUser
.updateProfile({
displayName: name,
})
.then(() => {
console.log("sign in successfully");
return data.user.getIdToken();
});
})
.then((token) => {
return db.doc(`/users/${newUser.handle}`).set("test");
})
.then(() => {
return res.status(201).json({ token });
})
.catch((err) => {
// probably send an error back?
// return res.status(500).json({ message: 'error' });
console.error(err);
});
};

How to migrate my mongoose PROMISE chain transactions to ASYNC / AWAIT flow?

I created an API that integrate database responses in a promise flow, but I think the interpretation of the code is complex and I believe that async / await approach could improve both understanding and the code itself.
The API is built in NodeJS using mongoose 5.6.1 and express 4.17.1.
Can you help me in improve this?
Below is the API that I want to improve:
/** New employee */
router.post('/', (req, res) => {
let { idCompany, name, departament } = req.body;
let _id = mongoose.Types.ObjectId(); // Generating new MongoDB _ID
let employeeCreated;
const promise1 = new Promise((resolve, reject) => {
// Querying by document '$oid'
Companies.findOne({ _id: idCompany }, (err, company) => {
// Error returned
if (err) reject({ error: "Invalid request, something went wrong!" });
// Invalid data received
if (!company) reject({ error: "Unauthorized action!" });
// Everything OK
resolve(company);
});
})
.then(company => {
if(company) {
const promise2 = new Promise((resolve, reject) => {
Employees.create({ _id, idCompany, name, departament }, (err, employee) => {
// Error returned
if (err) reject({ error: "Invalid request, something went wrong!", err });
// Everything OK
employeeCreated = employee;
resolve(company);
});
})
return promise2;
}else reject({ error: "Company not found!" });
})
.then(company => {
let { name: companyName, address, email, tel, employees } = company;
employees.push(_id);
const promise3 = new Promise((resolve, reject) => {
Companies.findByIdAndUpdate(
{ _id: idCompany },
{ $set: { _id: idCompany, name: companyName, address, email, tel, employees } }, // spotlight
{ new: true },
(err, company) => {
// Something wrong happens
if (err) reject({ success: false, error: "Can't update company!" });
// Everything OK
resolve(company);
}
);
});
return promise3;
});
promise1
.then(() => res.json({ success: true, employeeCreated }))
.catch(err => res.status(400).json({ error: "Invalid request, something went wrong!", err }));
});
Regards.
One key to using promises with mongoose, is using the exec method:
Your code could then look something like this (not tested):
router.post('/', async (req, res) => {
try {
const { idCompany, name, departament } = req.body;
const _id = mongoose.Types.ObjectId();
const company = await Companies.findOne({ _id: idCompany }).exec();
const employeeCreated = await Employees.create({ _id, idCompany, name, departament });
const { name: companyName, address, email, tel, employees } = company;
employees.push(_id);
await Companies.findByIdAndUpdate(
{ _id: idCompany },
{ $set: { _id: idCompany, name: companyName, address, email, tel, employees } }, // spotlight
{ new: true }).exec();
res.json({ success: true, employeeCreated });
} catch(err) {
res.status(400).json({ error: "Invalid request, something went wrong!", err });
}
});
You could throw some specific custom errors in the try block if you find that necessary.
You could simply make the functions where your promises are running async and so, you could await for the promises to resolve.
For example, in your route use this:
router.post('/', async (req, res) => {
and then when performing an async operation, use this:
const company = await Companies.findOne({ _id: idCompany }).exec();
Also, I would suggest you to wrap this with try and catch statments
Hope it helps!

should I validate uniques in Mongoose before using save?

I am new to Node and Javascript in general and I was wondering if I should validate uniqueness by using FindOne before using .save.
My User schema does have Unique:true set for email and username and my current code works like a charm since mongoose returns an error message for uniques.
I wanted to know if it was better to validate for uniqueness before attempting to save for effiency or something?
Current code as follow :
export const createUser = (data) => {
return new Promise( async (resolve, reject) => {
const userData = JSON.parse(data);
const newUser = new User(userData);
await newUser.save((err) => {
if(err){
const msg = err.errmsg.toLowerCase();
const errormsg = msg.includes('email') ? 'Email already in use' : msg.includes('username') ? 'Username already in use' : 'Unexpected error.'
reject(JSON.stringify({error: errormsg}));
}
resolve(JSON.stringify({status: 200, created: true}));
});
});
};
Implemented here :
public register(req, res){
validateRegisterForm(req.body).then(data => {
createUser(data).then(resp => {
res.send(resp);
}).catch(err => {
res.send(err);
})
}).catch(err => {
res.send(err);
});
}

How to execute bcrypt.compare inside Sequelize .then?

I'm trying to build a login page where I get the hashed password from mysql db using Sequelize and then calling bcrypt compare to dehash the password and compare it with the user's login input for authentication.
However, bcrypt compare is always executing slower than the return causing the value to always be "". I know this has to do with asynchronous behaviour but I don't know how to properly write this code to make it work.
authenticate: (req, res) => {
let userDetails = req.query;
User.findOne({
where: {
username: userDetails.username
}
})
.then((user) => {
// How can I make this so, correctPassword() finishes
// and then the authenticated variable will be either false or true?
let authenticated = correctPassword(userDetails.password, user.password);
return authenticated;
})
.then((authenticated) => {
// right now authenticated is "" in client side console.
res.send(authenticated);
})
.catch((error) => {
console.log('there was an error: ', error);
});
}
}
const correctPassword = (enteredPassword, originalPassword) => {
return bcrypt.compare(enteredPassword, originalPassword, (err, res) =>{
return res;
});
}
You're almost there. You correctly intuited that correctPassword executes asyncronously, though it is written as if it's syncronous.
First off, let's make correctPassword a promise, so we can use async/await or call .then on it
const correctPassword = (enteredPassword, originalPassword) => {
return new Promise(resolve => {
bcrypt.compare(enteredPassword, originalPassword, (err, res) =>{
resolve(res)
});
})
}
Next, you have two approaches to ensure the order of operations in your code executes correctly:
(Recommended) Use async/await syntax allowing us to write synchronous-looking code:
authenticate: async (req, res) => {
let userDetails = req.query;
try {
const user = await User.findOne({
where: {
username: userDetails.username
}
});
const authenticated = await correctPassword(userDetails.password, user.password);
res.send(authenticated);
} catch(e) {
res.status(400).send(e)
}
}
Continue using promises:
authenticate: (req, res) => {
let userDetails = req.query;
User.findOne({
where: {
username: userDetails.username
}
}).then(() => {
correctPassword(userDetails.password, user.password)
.then(authenticated => {
res.send(authenticated)
})
.catch(e => {
res.send(e)
})
})
}
You can't assign async function to variable which is used by sync code later on. If you want to do sync function, you can use await/aync. But in here I recommend you use promise for compare function as well.
User.findOne({
where: {
username: userDetails.username
}
})
.then((user) => {
return correctPassword(userDetails.password, user.password);
})
.then((authenticated) => {
res.send(authenticated);
})
Bcrypt also supports promise.
const correctPassword = (enteredPassword, originalPassword) => {
return bcrypt.compare(enteredPassword, originalPassword).then((res) =>{
return res;
});
}

Chaining ES6 promises without nesting

I'm trying to chain a second then method after the first one but it's not working correctly for some reason. It only works fine when I'm nesting the then method. Here's the code that doesn't work correctly:
auth.post('/signup', (req, res, next) => {
const { username } = req.body
const { password } = req.body
Users.findOne({ username })
.then(
existingUser => {
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
const user = new Users({ username, password })
user.save()
},
err => next(err)
)
.then(
savedUser => res.send({
username: savedUser.username,
password: savedUser.password
}),
err => next(err)
)
})
Here when I post to '/signup' user gets saved into the database but I don't get the response with username and password. However:
auth.post('/signup', (req, res, next) => {
const { username } = req.body
const { password } = req.body
Users.findOne({ username })
.then(
existingUser => {
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
const user = new Users({ username, password })
user.save()
.then(
savedUser => res.json({
username: savedUser.username,
password: savedUser.password
}),
err => next(err)
)
},
err => next(err)
)
})
This works as expected. user gets saved and I get the response with username and password. I've read that you can chain these then methods in a flat way without nesting. But I've checked questions on here and couldn't find an answer as to what I'm doing wrong here. Can someone please help with this issue?
Simple 3 step process:
Return a promise from the first .then call.
Change this:
// ...
const user = new Users({ username, password })
user.save()
// ...
to this:
// ...
const user = new Users({ username, password })
return user.save()
// ...
(Mind the return keyword, which will chain it with the second .then() call)
2. Reject the Promise in case existingUser returns false (thanks #JaromandaX for pointing out)
Change this:
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
to this:
if (existingUser) {
res.status(422).send({ error: 'Username is in use' });
return Promise.reject('USER_EXISTS');
}
3. Drop the .then(onResolvedFunction, onRejectedFunction) pattern when possible, and use .catch(err) instead (to catch for a bigger spectrum of errors).
Delete the second argument from your .then()'s
,
err => next(err)
use a .catch instead:
Users.findOne({ username })
.then(...)
.then(...)
.catch((e) => { // <-- Handle the error properly
console.log(e);
if (e !== 'USER_EXISTS')
next(err);
});
Mongoose Footnote!
This has nothing to do with promises. I see you named your model Users, but remember that, internally, Mongoose will pluralize your model names for you. You should either:
Name your model User; or
Explicitly set the pluralized form in a third argument, like this:
const Users = mongoose.model('User', UserSchema, 'Users');
You have at least three issues with your "chained" version
You are not returning anything from your first .then
in the case of existing user, the chained .then would still be executed
in the case of an rejection in Users.findOne the chained .then would also be executed
To fix:
simply return .save()
return a Promise.reject - alternatively you can throw an error
don't use onRejected functions in .then, just have a single rejection handler, at the end of the chain, in a .catch
I would chain that code like this:
auth.post('/signup', (req, res, next) => {
const { username } = req.body
const { password } = req.body
Users.findOne({ username })
.then(existingUser => {
if (existingUser) {
return Promise.reject({
status:422,
error: 'Username is in use'
});
}
return new Users({ username, password }).save();
})
.then(savedUser => res.send({
username: savedUser.username,
password: savedUser.password
}))
.catch(err => {
if (err.status) {
return res.status(err.status).send({ error: err.error });
}
return next(err);
});
});

Categories

Resources