Chain Multiply Promises into one chain - javascript

I have a sign-in logic with Express and Mongo. I need to put them into one promise chain and avoid nesting promises.
Here is my code:
const foundUser = User.findOne({ email: email }).exec();
const hashedPassword = bcrypt.hash(password, 10);
return Promise.all([foundUser, hashedPassword])
.then(arr => {
const [ user, hashedPassword ] = arr;
if (user) {
return res.status(400).json({
message: "This email has already occupied."
});
};
const newUser = new User({
fullName,
email,
password: hashedPassword
});
newUser.save()
.then(user => {
return res.status(200).json({
message: `${user.fullName}, you are successfully registered.`
});
})
.catch(err => {
console.log(err);
return res.status(500);
})
})
.catch(err => {
console.log(err);
return res.status(500);
});
I tried to use to combine them with Promise.all, but one of my promises depends on the previous one, so I added newUser.save() in my then.
I achieved that async/awaits like this:
const foundUser = await User.findOne({ email: email }).exec();
if (foundUser) {
return res.status(400).json({
message: "This email has already occupied."
});
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = new User({
fullName,
email,
password: hashedPassword
});
const user = await newUser.save();
return res.status(200).json({
message: `${user.fullName}, you are successfully registered.`
});
} catch (err) {
console.log( err);
return res.status(500).json({
message: 'Error'
})
}
But I wonder is there a way to do it with promises and without nesting.
I need to put them on one chain so I can avoid using multiply catches

As #Bergi mentioned in the comment, you can return a promise from your .then callback. Depending on if this returned promise gets resolved or rejected, your .then or .catch next in the chain is called. You can do something like this:
const foundUser = User.findOne({ email: email }).exec();
const hashedPassword = bcrypt.hash(password, 10);
return Promise.all([foundUser, hashedPassword])
.then(arr => {
const [ user, hashedPassword ] = arr;
if (user) {
return res.status(400).json({
message: "This email has already occupied."
});
};
const newUser = new User({
fullName,
email,
password: hashedPassword
});
return newUser.save()
})
.then(user => {
return res.status(200).json({
message: `${user.fullName}, you are successfully registered.`
});
})
.catch(err => {
console.log(err);
return res.status(500);
});

Related

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

Why am I getting Promise Pending despite using await in my controller?

I have a repository where I connect directly to my model to insert some data, it creates the data successfully but when I connect my controller to this repository, I get a nulled response, if I log it in the repository itself I get Promise . Please checkout my code below:-
Repository.js
exports.register = (request) => {
const data = UserModel.findOne({email: request.email})
.then(user => {
if(user)
{
return {status: 400, message: 'Email Already exist'}
} else {
return bcrypt.genSalt(10, (err, salt) => {
const newUser = new UserModel({
username: request.username,
email: request.email,
password: request.password
});
return bcrypt.hash(newUser.password, salt, async (err, hash) => {
if(err) throw err;
newUser.password = hash;
return newUser.save()
.then(user => {
const token = jwt.sign({id: user._id}, process.env.JWT_SECRET, {
expiresIn: 86400 // expires in 24 hours
});
return {status: 200, message: 'Successfully Registered', auth: true, token: token, user: user}
})
.catch(err => {
return {status: 400, message: err}
})
})
})
}
})
console.log(data) // This part is return Promise <pending>
return data;
};
Controller.js
exports.SeedRegisteration = async (req, res, next) => {
try {
let element = await userRepo.register({username: "Testin", email: "Testin#test.com", "password":
"joe" });
return await res.status(200).json({ status: 200, data: element })
} catch (e) {
return res.status(400).json({ status: 400, message: e.message });
}
};
Works fine but does not return data
Here's the register function using the Promise version of bcrypt (if you don't supply a callback, the bcrypt functions return a Promise
exports.register = (request) =>
UserModel.findOne({
email: request.email
})
.then(user => {
if (user) {
throw 'Email Already exist'
}
})
.then(() => bcrypt.genSalt(10))
.then(salt => {
const newUser = new UserModel({
username: request.username,
email: request.email,
password: request.password
});
return bcrypt.hash(newUser.password, salt)
.then((hash) => {
newUser.password = hash;
return newUser.save();
})
}).then(user => {
const token = jwt.sign({
id: user._id
}, process.env.JWT_SECRET, {
expiresIn: 86400 // expires in 24 hours
});
return {
status: 200,
message: 'Successfully Registered',
auth: true,
token: token,
user: user
}
}).catch(err => {
return {
status: 400,
message: err
}
});
Note: there is ONE nested .then - this code could be perfectly flat if you used async/await in register - however I was not prepared to perform such a big rewrite for the answer. Now that the code is in a nice almost flat promise chain, it's relatively simple to convert the whole thing into async/await style
There are too many return statements which return promise. Please update your code in to the following:
exports.register = (request) => {
return new Promise((resolve, reject) => {
try {
UserModel.findOne({ email: request.email })
.then(user => {
if (user) {
return reject({ status: 400, message: 'Email Already exist' })
} else {
bcrypt.genSalt(10, (err, salt) => {
const newUser = new UserModel({
username: request.username,
email: request.email,
password: request.password
});
bcrypt.hash(newUser.password, salt, async (err, hash) => {
if (err) return reject(err);
newUser.password = hash;
newUser.save()
.then(user => {
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
expiresIn: 86400 // expires in 24 hours
});
return resolve({ status: 200, message: 'Successfully Registered', auth: true, token: token, user: user })
})
.catch(err => {
return reject({ status: 400, message: err })
})
})
})
}
}).catch(err => {
return reject(err)
})
} catch (error) {
return reject(error)
}
});
};

Cannot set headers after they are sent to the client with mongoose and node.js

I'm trying to make a registration function for users. I want to check first if the email exist, if so return a json
{ message: 'cannot register a new user' }
or else a json with confirmation and registered user details.
this code works fine, however the compiler says:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
its seems the the problem is this line:
res.status(200).json({ message: 'A new user was created!', user: result });
but I don't know how fix it so it won't make this message
my code is:
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
exports.signup = async (req, res, next) => {
const firstname = req.body.firstname;
const lastname = req.body.lastname;
const email = req.body.email;
const password = req.body.password;
try {
let canRegister = await User.findOne({ email: email })
.then(user => {
if (!user) {
return true;
}
res.status(400).json({ message: 'Email is already in use' });
})
.catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
});
let addUser = await bcrypt
.hash(password, 12)
.then(hashedPw => {
const user = new User({
firstname: firstname,
lastname: lastname,
email: email,
password: hashedPw
});
return user.save();
})
.then(result => {
res.status(200).json({ message: 'A new user was created!', user: result });
})
.catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
});
} catch {
res.status(400).json({ message: 'Email is already in use' });
}
};
You are trying to send the response twice.
let canRegister = await User.findOne({ email: email })
.then(user => {
if (!user) {
return true;
}
// You might have executed this 1st - and continue.
res.status(400).json({ message: 'Email is already in use' });
})
.catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
});
But your code doesn't exit. It moves onto the next block.
let addUser = await bcrypt
.hash(password, 12)
.then(hashedPw => {
const user = new User({
firstname: firstname,
lastname: lastname,
email: email,
password: hashedPw
});
return user.save();
})
.then(result => {
// Then you are sending status again with this line.
res.status(200).json({ message: 'A new user was created!', user: result });
})
.catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
});
You should figure out after canRegister is assigned (await block is complete) and return appropriately before proceeding to the next block.
Something like this:
let canRegister = await User.findOne({ email: email })
.then(user => {
if (!user) {
return true;
}
return false;
// Don't use the res.status here.
})
.catch(err => {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
});
if (!canRegister) {
return res.status(400).json({ message: 'Email is already in use' });
}

Throw errors outside of promise

I have a function to log in a user which should return JSON.
const username = req.body.username;
const password = req.body.password;
if (!username) {
throw new Error('Missing username');
}
if (!password) {
throw new Error('Missing password');
}
User.findOne({ username, password }).then(user => {
res.json({ user });
}).catch(err => {
res.json({ err });
});
but then the errors for missing username or missing password are not returned in JSON.
I could change it to
const username = req.body.username;
const password = req.body.password;
if (!username) {
res.json({ err: 'Missing username' });
}
if (!password) {
res.json({ err: 'Missing password' });
}
User.findOne({ username, password }).then(user => {
res.json({ user });
}).catch(err => {
res.json({ err });
});
but it seems a little redundant.
Is the correct way to do it to encapsulate it in a promise?
In your first solution, the thrown errors won't be handled, because you throw them outside of promise chain and without try/catch block. In your second solution you can get cannot send headers after they sent error, because the response can be sent twice (username is missing and password is missing).
So the one of the possible solutions here, is to create a promise chain (using Promise.resolve()) and validate parameters here:
function validateParams() {
const username = req.body.username;
const password = req.body.password;
if (!username) {
throw new Error('Missing username');
}
if (!password) {
throw new Error('Missing password');
}
return { username, password };
}
Promise
.resolve()
.then(validateParams)
.then(filter => User.findOne(filter))
.then(user => res.json(user))
.catch(err => res.json(err));
The obvious way would indeed be to encapsulate them in a promise to start your promise chain (with the User.findOne being inside the first then-block) - that way your current error handler catches them just fine.
I'm taking the example from #alexmac and use es6 async feature:
function validateParams() {
const username = req.body.username;
const password = req.body.password;
if (!username) {
throw new Error('Missing username');
}
if (!password) {
throw new Error('Missing password');
}
return { username, password };
}
async function resolver() {
try {
await resolve()
let filter = validateParams()
let user = await User.findOne(filter)
await res.json(user)
} catch (e) {
await res.json(e)
}
}
and that would look more elegant by using an if instead of a throw:
async function(req, res) {
const password = req.body.password
const username = req.body.username
let c = !password ? 'missing password' :
!username ? 'missing username' : null
if (!c) {
c = await User.findOne({ username, password })
}
await res.json(c)
}
you can wrap your functions in a promise and handle it efficiently
function getRes(){
return new Promise(function(resolve, reject){
const username = req.body.username;
const password = req.body.password;
if (!username) {
reject(new Error('Missing username'));
}
if (!password) {
reject(new Error('Missing password'));
}
resolve(User.findOne({ username, password }));
});
}
getRes().then(function(result){
res.json(result);
}).catch(function(err){
res.json(err);
})

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