node.js chaining multiple async function using mongoose - javascript

I have a function that look like this, for now at least it's working.
exports.changePassword = (req, res) => {
const { token, password, confirmPassword } = req.body
User.findOne({resetPasswordToken: token}, (err, user)=>{
if(!user){
return res.status(400).send({
msg: 'Invalid token or token has been used!'
})
}
const hash_password = bcrypt.hashSync(password, 10)
User.findOneAndUpdate({_id: user._id},
{hash_password},
(err, result)=>{
if(err){
return res.status(400).send({
msg: err
})
}
User.findOneAndUpdate({_id: user._id},
{resetPasswordToken: ''},
(err, result)=>{
if(err){
return res.status(400).send({
msg: err
})
}
res.status(200).json({
status: 1,
data: 'Your password has been changed.'
})
}
)
})
})
}
I just felt bad writing this block of code, because I think it has several problems:
callback hell
duplication of error handling code
For first problem maybe I can use done argument? and do some chaining? And also sometime I doubt I need to handle every single err callback. How would you rewrite above function to become more elegant?

You can use promises with Mongoose, which will help with your callback hell:
exports.changePassword = (req, res) => {
const { token, password, confirmPassword } = req.body
User.findOne({resetPasswordToken: token}).then((user)=>{
// do stuff with user here
const hash_password = bcrypt.hashSync(password, 10)
// Now chain the next promise by returning it
return User.findOneAndUpdate({_id: user._id}, {hash_password});
}).then((result)=>{
// Now you have the result from the next promise, carry on...
res.status(200).json({
status: 1,
data: 'Your password has been changed.'
})
}).catch(err => {
// Handle any errors caught along the way
});
}
Since these are promises, you can actually make this even neater by using the ES6 async/await syntax:
// Note this now has the async keyword to make it an async function
exports.changePassword = async (req, res) => {
const { token, password, confirmPassword } = req.body
try {
// Here is the await keyword
const user = await User.findOne({resetPasswordToken: token});
// do stuff with user here
const hash_password = bcrypt.hashSync(password, 10)
// Now the next promise can also be awaited
const result = await User.findOneAndUpdate({_id: user._id}, {hash_password});
// Finally send the status
res.status(200).json({
status: 1,
data: 'Your password has been changed.'
});
} catch (err) {
// Any promise rejections along the way will be caught here
});
}

To avoid this ugly Promise hell we have
ES2017 async/await syntax
You should change your whole code for something like this
exports.changePassword = async function (req, res){
try {
const { token, password, confirmPassword } = req.body
var user = await User.findOne({resetPasswordToken: token}).exec()
const hash_password = bcrypt.hashSync(password, 10)
var result = await User.findOneAndUpdate({_id: user._id}, {hash_password}).exec()
var result2 = await User.findOneAndUpdate({_id: user._id}, {resetPasswordToken: ''}).exec()
res.status(200).json({status: 1, data: 'Your password has been changed.'})
} catch (err) {
res.status(400).send({msg: err }) //If some await reject, you catch it here
}
}

Related

Express + login script

My login script was working with Postman, but as soon as I attempted to get React.js to post it to the server it started breaking.
The issue is that, when it checks for the user, it is not able to pass back the return result For some reason it just does not do anything.
Login route:
router.post('/login', async (req,res) => {
console.error(req.body.password)
try {
var check = await checkUser(req.body.email, req.body.password).catch((result) => {
console.log(result)
})
} catch(err){
console.error(err)
}
});
Check function:
async function checkUser(username, password) {
var username = username;
var password = password;
var result;
await Users.findOne({email: username}, async function (err, docs) {
// console.error(docs);
if (err) {
console.log(err)
} else {
const match = await bcrypt.compare(password, docs.password);
if(match) {
result = {userid:docs._id, success:"true"}
//result['userid'] = docs._id;
//result['success'] = true;
} else{
result = {success:"false"}
// result['success'] = false;
}
}
// console.error(result);
return result;
})
}
Wondering if anyone can see where I am going wrong.
Dont use .catch() if you go with async / await. To catching errors you already have try / catch
router.post('/login', async (req,res) => {
console.error(req.body.password)
try {
var check = await checkUser(req.body.email, req.body.password)
} catch(err){
console.error(err)
}
});
Down there similar problem. You use the callback approach with async / await. The reason why you doesnt get anything back is that you returning your result to your callback function ...async function().. if you take a closer look.
However as i said, dont use callback + async / await
Remember use either: async / await or chaining .then() .catch() or use the callback approach but not all together its bad and leads to unessecary bugs.
async function checkUser(username, password) {
var username = username;
var password = password;
var result;
try {
let user = await Users.findOne({ email: username });
if (!user) return "no user found";
const match = await bcrypt.compare(password, user.password);
if (match) {
result = { userid: user._id, success: "true" };
} else {
result = { success: "false" };
}
return result;
} catch (err) {
console.log(err);
}
}

How to use chained Promises calls?

I'm trying to implement a simple login function.
module.exports.login = (req, res, next) => {
let loggedin_user;
User.findOne({email: req.body.email.toLowerCase()})
.then(user => {
if(!user){
throw ('Invalid e-mail or password');
}
loggedin_user = user;
return bcryptjs.compare(req.body.password, user.password)
})
.then(res => {
if(!res){
return res.status(401).json('Invalid e-mail or password')
}
const token = jwt.sign({
id: loggedin_user._id,
role: loggedin_user.role
}, process.env.JWT_KEY, { expiresIn: '24h' });
return res.status(200).json({
token: token,
role: loggedin_user.role,
expires_in: 24*60*60})
})
.catch(err => {
return res.status(401).json(err);
})
}
My code works great until it reaches the last return part, this part:
return res.status(200).json({
token: token,
role: loggedin_user.role,
expires_in: 24*60*60,
})
It doesn't return anything instead it jumps to the catch block, although it console logs that javascript object that I need to return, it logs it right before the return statement.
What's the problem?
You should log the error message in the catch to see what the error is.
I'd suspect req.body or user may be undefined, and checking the properties email and password could result in an error.
At first glance, I don't catch any error to your code. Maybe issue is with your password, but I am not sure.
Any way to simplify things and check the same, I modified your code as below. Try this and let me know the output. To deal with Promises, Async/Await is better.
module.exports.login = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email.toLowerCase() });
if (!user) {
res.status(401).json('Invalid e-mail');
}
const checkPass = await bcryptjs.compare(req.body.password, user.password);
if (!checkPass) {
res.status(401).json('Invalid password');
}
const token = jwt.sign(
{
id: user._id,
role: user.role,
},
process.env.JWT_KEY,
{ expiresIn: '24h' }
);
res.status(200).json({
token: token,
role: user.role,
expires_in: 24 * 60 * 60,
});
} catch (err) {
console.error(err.message);
}
};

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!

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

I'm doing Promises better, but still kind of wrong... One more thing to clear up

I asked a question about JS Promises in this post:
I'm doing Promises wrong... What am I missing here?
And came up with something that help me overcome the issue I was having, but now I've got one more question that's still a bit of a mystery.
In the updated code I have:
login.ts:
import { Router } from 'express-tsc';
import { db, dbUserLevel } from '../../util/db';
import * as bodyParser from 'body-parser';
import { genToken } from '../../util/token';
import * as jwt from 'jsonwebtoken';
export var router = Router();
let urlencodedParser = bodyParser.urlencoded({ extended: false });
let jsonParser = bodyParser.json();
router.post('/', jsonParser, (req, res) => {
req.accepts(['json', 'text/plain']);
let data = req.body;
console.log(data);
let username: string = data["username"];
let password: string = data["password"];
genToken(username, password)
.then(token => {
res.status(200).send(token);
})
.catch(err => {
res.status(500).send(err);
});
});
The issue I'm now having is described in the commented code of the snippet below:
token.ts :
import * as jwt from 'jsonwebtoken';
import { db, dbUserLevel } from '../util/db';
export function genToken(username, password) {
let token_payload = { user: username, admin: false };
let token_payload_admin = { user: username, admin: true };
// TODO: Add secret as an environment variable and retrieve it from there
let token_secret = 'move this secret somewhere else';
let token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
let token: Object;
let query = db.open()
.then(() => dbUserLevel('user'))
// If above is successful, this .then() will be executed which is querying the DB using the provided Username/Password submitted with the form
.then(() => db.collection('users').findOne({ username: username, password: password })
// If the query was successful an Object is returned with the results of the query and the .then() below is executed to analyze the result
.then((result) => {
if (result.isAdmin === 1) {
// If the "isAdmin" property of the returned Object is "1", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload_admin, token_secret, token_header) }
} else if (result.isAdmin === 0) {
// If the "isAdmin" property of the returned Object is "0", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload, token_secret, token_header) }
}
})
// The question is here... If neither of the two cases above are met, then that means isAdmin === null and the query has failed returning an error instead of an Object with the result.
// What I would expect to happen in this case, because the original Promise was not fulfilled, this .catch() should be called.
// Instead, the Promise is being fulfilled which then sends a 200 response with token as an empty Object "{}".
// How do I get this .catch() to reject the Promise and send the 500 response instead?
.catch(err => {
db.close();
Promise.reject(err);
}))
.then(() => {
db.close();
Promise.resolve(token);
return token;
})
.catch(err => {
db.close();
Promise.reject(err);
return err;
});
return query;
};
Your problem is that you missed to return the Promise.reject(…)s from your callbacks. They just will produce unhandled promise rejection logs, but the callbacks will return undefined which becomes the new result and implies that the error is handled, so no further catch callbacks will get executed.
However, that code should be simplified a lot anyway. Regarding the closing of the database connection, you should have a look at the disposer pattern or a finally method.
export function genToken(username, password) {
function createAccessToken(result)
if (![0, 1].includes(result.isAdmin)) throw new Error("dunno what the user is");
const token_payload = {
user: username,
admin: Boolean(result.isAdmin)
};
const token_secret = 'move this secret somewhere else';
const token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
return jwt.sign(token_payload, token_secret, token_header);
}
return db.open()
.then(() => dbUserLevel('user'))
.then(() => db.collection('users').findOne({ username: username, password: password }))
.then(result => ({access_token: createAccessToken(result)}))
.then(token => {
db.close();
return token;
}, err => {
db.close();
throw err;
});
}

Categories

Resources