How to avoid 'headers already sent' within Promise chain? - javascript

I am working on a 'change password' functionality. I am starting to learn more about Promises and have the following code:
router.post('/change-password', verifyToken, csrfProtection, (req, res, next) => {
if (!req.body.password_current || !req.body.password_new) {
req.flash('info', 'Please fill in both fields.');
return res.redirect('/change-password');
}
const data = {};
data.password = req.body.password_new;
tokenHandler.verifyToken(req.cookies.token)
.then((decoded) => {
return User.findOne({ '_id.user_id': decoded.user });
})
.then((user) => {
data.userId = ObjectId(user._id.user_id);
return bcrypt.compare(req.body.password_current, user.password);
})
.then((allowed) => {
if (!allowed) {
return res.redirect('/change-password');
}
console.log('I am not here');
return User.findOneAndUpdate({ '_id.user_id': data.userId }, { password: data.password }, { new: true });
})
.then(() => {
return res.redirect('/change-password');
})
.catch((err) => {
return next(err);
});
});
I love how Promises are preventing the 'callback hell'. The problem is that I am receiving a 'headers already sent' error. I know that is because I can't escape the chain and that it saves up all the results (unless you throw an Error). To fix the problem I used the following:
router.post('/change-password', verifyToken, csrfProtection, (req, res, next) => {
if (!req.body.password_current || !req.body.password_new) {
req.flash('info', 'Please fill in both fields.');
return res.redirect('/change-password');
}
const data = {};
data.password = req.body.password_new;
tokenHandler.verifyToken(req.cookies.token)
.then((decoded) => {
User.findOne({ '_id.user_id': decoded.user }).then((user) => {
data.userId = ObjectId(user._id.user_id);
bcrypt.compare(req.body.password_current, user.password).then((allowed) => {
if (!allowed) {
return res.redirect('/change-password');
}
User.findOneAndUpdate({ '_id.user_id': data.userId }, { password: data.password }).then((doc) => {
console.log(doc);
return res.redirect('/change-password');
});
});
});
});
});
The question is: Is there a better solution to fix the 'header already sent' error. Because I have the feeling that my solution is actually a few steps away from a 'callback hell' structure.

You can rewrite it like this
router.post('/change-password', verifyToken, csrfProtection, (req, res, next) => {
if (!req.body.password_current || !req.body.password_new) {
req.flash('info', 'Please fill in both fields.');
return res.redirect('/change-password');
}
const data = {};
data.password = req.body.password_new;
tokenHandler.verifyToken(req.cookies.token)
.then((decoded) => {
return User.findOne({ '_id.user_id': decoded.user });
})
.then((user) => {
data.userId = ObjectId(user._id.user_id);
return bcrypt.compare(req.body.password_current, user.password);
})
.then((allowed) => {
if (!allowed) {
return res.redirect('/change-password');
}
else{
console.log('I am not here');
return User.findOneAndUpdate({ '_id.user_id': data.userId }, { password: data.password }, { new: true })
.then(() => {
return res.redirect('/change-password');
});
}
})
.catch((err) => {
return next(err);
});
});
You can return a promise chain from within a then function.

Depending on your version of Node, you may also be able to re-write this using async / await. It generally makes things easier to reason about.
router.post('/change-password', verifyToken, csrfProtection, async (req, res, next) => {
if (!req.body.password_current || !req.body.password_new) {
req.flash('info', 'Please fill in both fields.');
return res.redirect('/change-password');
}
try {
const data = {};
data.password = req.body.password_new;
const decoded = await tokenHandler.verifyToken(req.cookies.token);
const user = await User.findOne({ '_id.user_id': decoded.user });
data.userId = ObjectId(user._id.user_id);
const allowed = await bcrypt.compare(req.body.password_current, user.password);
if (!allowed) {
return res.redirect('/change-password');
} else {
await User.findOneAndUpdate({ '_id.user_id': data.userId }, { password: data.password }, { new: true });
}
return res.redirect('/change-password');
} catch (err) {
return next(err);
}
});
You need Node.js >= 7 to use async/await.

Related

How to make your code wait for execution of loop

Following is my getUser function
const getUsers = async (req, res) => {
try {
ddb.get({
TableName: "Tablename",
Key: { Username: req.query.username }
},(err,user) => {
if(err || Object.keys(user).length === 0) {
return res.status(404).json("Something went wrong")
} else {
const userSociety = Object.keys(user.Item.Societies)[0]
ddb.get({
TableName: "Tablename",
Key: { SocietyCode: userSociety }
},(err, society) => {
if(err || Object.keys(society).length === 0) {
return res.status(400).json({ message: "Could not fetch society members" })
} else {
const users = Object.keys(society.Item.SocietyMembers)
const usersData = []
users.forEach(async u => {
ddb.get({
TableName: "TestMyMohallaUsers",
Key: { Username: u }
},async (err,user) => {
if(err || Object.keys(user).length === 0) {
} else usersData.push({
Username: user.Item.Username,
Firstname: user.Item.Name
})
})
})
return res.status(200).json({ message: "Data detched successfully", Users: usersData })
}
})
}
})
} catch (error) {
return res.status(500).json({ error: "Internal Server Error" })
}
}
I want to wait for the execution of forEach and then send back the data via return statement but as of now the return statement gives empty array of users.
Clearly my code in not waiting for the execution of forEach and then returning the data. How can I do that someone help me?
Edit: ddb is an instance of DynamoDB
You'll have a better time if you
use the DynamoDB Promise API instead of a pyramid of callbacks
refactor your code to a couple of functions
Finally, awaiting for all user fetches to complete requires Promise.all for all of those promises.
async function getUser(ddb, username) {
const user = await ddb
.get({
TableName: "TestMyMohallaUsers",
Key: { Username: username },
})
.promise();
if (!user.Item) {
throw new Error(`User ${username} not found`);
}
return user.Item;
}
async function getSociety(ddb, societyCode) {
const society = await ddb
.get({
TableName: "Tablename",
Key: { SocietyCode: societyCode },
})
.promise();
if (!society.Item) {
throw new Error(`Society ${societyCode} not found`);
}
return society.Item;
}
const getUsers = async (req, res) => {
try {
const user = await getUser(ddb, req.params.username);
const userSocietyCode = Object.keys(user.Societies)[0];
const society = await getSociety(ddb, userSocietyCode);
const societyUsers = Object.keys(society.SocietyMembers);
const usersData = await Promise.all(
societyUsers.map(async (member) => {
const user = await getUser(ddb, member);
return {
Username: user.Username,
Firstname: user.Name,
};
}),
);
return res
.status(200)
.json({
message: "Data detched successfully",
Users: usersData,
});
} catch (e) {
return res
.status(400)
.json({ message: `Could not fetch information: ${e}` });
}
};

async function not returns value but showing after calling undefined typescript

Below is my function returning login token when I debug my function it waits until return but when call function returns undefined and errors are also undefined don't know why it's happening
import userModel from '../Models/user.model';
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken')
let token = null;
process.env.SECRET_KEY = 'secret';
export default class loginController{
static async login(user:any): Promise<any>{
try{
await userModel.findOne({
Email: user.Email
})
.then(async (res:any) => {
if (user) {
if (await bcrypt.compareSync(user.Password, res.Password)) {
const payload = {
Firstname: res.Firstname,
Lastname: res.Lastname,
email: res.Email,
}
token = await jwt.sign(payload, process.env.SECRET_KEY, {
expiresIn: 1400
})
let decoded = jwt.verify(token, process.env.SECRET_KEY)
return token;
}
else {
return "Password is Wrong";
}
}
else {
return 'Please Check Username';
}
})
.catch(err => {
return('error : ' + err)
})
}
catch(err)
{
return err
}
}
}
And my calling function is
const router : Router = Router();
router.post('/login', async (req, res, next) => {
try {
const user = await loginController.login(req.body);
res.json(user)
} catch (error) {
res.json(error)
}
})
I tried call errors it's also debugger waiting until returns value to error but showing undefined
Thanks for the help!
login function doesn't return token because of function scoping. If you have multiple callbacks you can wrap it with a new Promise and use resolve function for returning values.
export default class loginController {
static async login(user: any): Promise<any> {
try {
return new Promise(async (resolve, reject) => {
await userModel
.findOne({
Email: user.Email
})
.then(async res => {
if (user) {
if (await bcrypt.compareSync(user.Password, res.Password)) {
const payload = {
Firstname: res.Firstname,
Lastname: res.Lastname,
email: res.Email
};
const token = await jwt.sign(payload, process.env.SECRET_KEY, {
expiresIn: 1400
});
let decoded = jwt.verify(token, process.env.SECRET_KEY);
resolve(token);
} else {
resolve('Password is Wrong');
}
} else {
resolve('Please Check Username');
}
})
.catch(err => {
resolve('error : ' + err);
});
});
} catch (error) {
return error;
}
}
}

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

Mongoose .catch block is the same in multiple places. How do I avoid this?

I use mongoose promises in my Node REST api and I have a lot of .catch blocks that are the same.
Here is a short route:
router.get('/', (req, res) => {
Post.find()
.populate('category', 'name')
.exec()
.then(posts => {
res.status(200).json(posts);
})
.catch(err => {
log.error(err);
res.status(500).json(err);
});
});
I have some routes where that exact .catch block appears three times in the same function.
Can I create a function where to have the log and result and pass the function to the .catch? Or can I use some middleware? How can I clean my code a bit?
Here is the mess:
router.delete('/:commentId', checkAuth, (req, res) => {
// Find the comment
Comment.findById(req.params.commentId).exec()
.then(comment => {
foundComment = comment;
// Delete the comment
Comment.deleteOne({ _id: req.params.commentId }).exec()
.then(result => {
if (result.deletedCount) {
// If the comment has replies, remove them as well
if (foundComment.replies.length > 0) {
Comment.deleteMany({ _id: { $in: foundComment.replies } }).exec()
.then(result => {
if (result.deletedCount) {
res.status(200).json({
deletedCount: result.deletedCount + 1
});
}
})
.catch(err => {
log.error(err);
res.status(500).json(err);
});
return;
}
res.status(200).json({
deletedCount: result.deletedCount
});
} else {
res.status(404).json({
message: `Comment with id ${req.params.commentId} doesn't exist.`
});
}
})
.catch(err => {
log.error(err);
res.status(500).json(err);
});
})
.catch(err => {
log.error(err);
res.status(500).json(err);
});
});
Return each of the Promises in the Promise chain, so that they're all tied together, allowing you to put a single .catch at the very end, which will run if anything throws inside:
router.delete('/:commentId', checkAuth, (req, res) => {
let foundComment;
Comment.findById(req.params.commentId).exec()
.then(comment => {
foundComment = comment;
return Comment.deleteOne({ _id: req.params.commentId }).exec();
})
.then(result => {
if (!result.deletedCount) {
res.status(404).json({
message: `Comment with id ${req.params.commentId} doesn't exist.`
});
return;
}
if (foundComment.replies.length <= 0) {
res.status(200).json({
deletedCount: result.deletedCount
});
return;
}
return Comment.deleteMany({ _id: { $in: foundComment.replies } }).exec()
.then(result => {
if (result.deletedCount) {
res.status(200).json({
deletedCount: result.deletedCount + 1
});
}
});
})
.catch(err => {
log.error(err);
res.status(500).json(err);
});
});
Generally, try to avoid nesting .thens if you can help it - flatter code is more readable code. async/await will make things even clearer:
router.delete('/:commentId', checkAuth, async (req, res) => {
try {
const foundComment = await Comment.findById(req.params.commentId).exec();
const deleteOneResult = await Comment.deleteOne({ _id: req.params.commentId }).exec();
if (!deleteOneResult.deletedCount) {
res.status(404).json({
message: `Comment with id ${req.params.commentId} doesn't exist.`
});
return;
}
if (foundComment.replies.length <= 0) {
res.status(200).json({
deletedCount: result.deletedCount
});
return;
}
const deleteManyResult = await Comment.deleteMany({ _id: { $in: foundComment.replies } }).exec()
if (deleteManyResult.deletedCount) {
res.status(200).json({
deletedCount: deleteManyResult.deletedCount + 1
});
}
} catch (err) {
log.error(err);
res.status(500).json(err);
}
});
I suggest changing your code to use async/await and use try/catch to handle error.
router.get('/', async (req, res) => {
try {
let posts = await Post.find().populate('category', 'name');
let comment = await Comment.findById(req.params.commentId);
if (!comment) {
return res.status(404).json({
message: `Comment with id ${req.params.commentId} doesn't exist.`
});
}
// Another query...
res.status(200).json(posts);
} catch(err) {
log.error(err);
res.status(500).json(err);
});
});
You could pass a function directly to .catch like:
function handleError(err) {
console.error('the error:', err.message)
}
const promise = new Promise((resolve, reject) => reject(new Error('your error')))
promise.catch(handleError)

Cannot read property 'then' of undefined in nodejs

I have no idea why I encountered that error in my code.
fineOneBySocialLogin(profile).then(function (user) {
}, function (err) {
return done(err, null);
})
var fineOneBySocialLogin = function (req, res) {
auth.findOne({ username: req.emails[0].value }).then(function (user) {
if (!user) {
console.log('testing 1');
var userForm = {};
userForm = {
email: req.emails[0].value
};
user.createUser(userForm).then(function(user) {
if (user) {
console.log('testing 2');
auth.findOne({ username: req.emails[0].value }).then(function (user) {
if (user) {
console.log('testing 3');
return user;
}
});
}
});
} else {
return user;
}
});
}
You should add return before auth.findOne in the second raw.
var fineOneBySocialLogin = function (req, res) {
return auth.findOne({ username: req.emails[0].value }).then(...
should be
return auth.findOne(...

Categories

Resources