Chaining ES6 promises without nesting - javascript

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

Related

Chain Multiply Promises into one chain

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

node.js chaining multiple async function using mongoose

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

Javascript - Giving the wrong status Code during tests

I am running tests for signin controller, it keeps giving the wrong status code (401) instead of 200 as i programmed it to be.
I expect it to use the data stored when a user signs up and return it if the given input is correct.
It works perfectly in postman but as i am writing tests, it throws the 401 error.
It is like it does not find the user
This is test block for the sign in:
it('it should signin a new user', (done) => {
request(app)
.post('/api/users/signin')
.send({
username: "Charles",
password: "challenger",
})
.expect(200)
.end((err, res) => {
if (err) {
return done(err);
}
done()
});
});
This is my controller for Logging in:
signin(req, res) {
const username = req.body.username.toLowerCase().trim();
// const email = req.body.email.trim();
if(!username) {
return res.status(401)
.send(
{status: false,
message: "Username cannot be empty"
});
}
else if (!req.body.password) {
return res.status(401)
.send({
status: false,
message: "Password field cannot be empty"
});
}
return User.findOne({
where: {
username,
}
})
.then((user) =>{
if(!user) {
return res.status(401).send({message: "User is not registered"})
}
else if(!user.validPassword(req.body.password)){
return res.status(401)
.send({
message: "The password is incorrect"
})
}
const token = user.generateAuthToken();
res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
});
})
.catch(error => {return res.status(400).send(error)})
},
This is the error i get:
1) Testing API routes POST /api/users/ it should signin a new user:
Error: expected 200 "OK", got 401 "Unauthorized"
at Test._assertStatus (node_modules\supertest\lib\test.js:266:12)
at Test._assertFunction (node_modules\supertest\lib\test.js:281:11)
at Test.assert (node_modules\supertest\lib\test.js:171:18)
at Server.assert (node_modules\supertest\lib\test.js:131:12)
at emitCloseNT (net.js:1552:8)
at _combinedTickCallback (internal/process/next_tick.js:77:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
I would put a bunch of console.log() in there to see exactly which code is firing since you have 4 chances to fire the 401.
Here is some code for you to examine:
// I don't understand enough context, so I have to re-write this
// to show you how it could be an async function which will
// return a promise, but will also allow you to await.
// async function sign(req, res) {
const sign = async (req, res) => { // This is same as above line
const username = req.body.username.toLowerCase().trim();
// const email = req.body.email.trim();
if (!username) {
console.log('username was empty')
return res.status(401).send({
status: false,
message: "Username cannot be empty"
});
}
if (!req.body.password) {
console.log('password was empty')
return res.status(401).send({
status: false,
message: "Password field cannot be empty"
});
}
return await User.findOne({ where: { username } })
// I'm making this one async also to ensure user.generateAuthToken()
// has a value before it proceeds to res.send()
.then(async (user) => {
if (!user) {
console.log('couldnt find user')
return res.status(401).send({
message: "User is not registered"
})
}
else if (!user.validPassword(req.body.password)){
console.log('password was incorrect')
return res.status(401).send({
message: "The password is incorrect"
})
}
const token = await user.generateAuthToken();
// I added a return here
return res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
});
})
.catch((error) => {
console.log('lets put data in here: ' + error)
return res.status(400).send(error)
})
},
I notice the MongoDB search User.findOne({ where: { username } }). I can't remember if it needs to be $where. I think MongoDB syntax uses a $. That could be your problem, and it will fire that console.log('couldnt find user') if so. That might be only for native MongoDB driver. I just Googled and found that the syntax could also be: User.findOne({ username }) which is shorthand for User.findOne({ username: username }).
Some people will tell you it is redundant to do return await fn() and omit the await, but it will throw an unhandled promise rejection if the promise is rejected. It will be caught if await is there. This is part of the upper scope's error handling architecture.
I recommend watching some async/await tutorials, because I see you mixing in a little bit of callback sauce. Your code is pretty good, but I think you can take it to the next level. It looks like you're ready.
Fun fact, you can also omit { and } if your if statement only has one expression, ie:
if (err) {
throw err;
}
can be shorthand:
if (err) throw err;
This can go a long way to help clean up the code, but async/await syntax with try/catch blocks used properly along with throw will do incredible improvements to synchronous looking code with minimal nesting.
Here is how you could re-write some of this, because I want to show you how we can get rid of nesting that adds confusion to your flow control:
const sign = async (req, res) => {
try {
const username = req.body.username.toLowerCase().trim()
if (!username) throw 'noUsername'
if (!req.body.password) throw 'noPassword'
const foundUser = await User.findOne({ username })
if (!foundUser.username) throw 'notFound'
// I assume this returns Boolean
const validPassword = await user.validPassword(req.body.password)
if (!validPassword) throw 'invalidPassword'
// Alter generateAuthToken() to throw 'badToken' if it fails
const token = await user.generateAuthToken()
return res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
})
} catch (error) {
// errors are manually thrown into here, and rejected promises
// are automatically thrown into here
if (error === 'noUsername') return res.status(401).send({
status: false,
message: 'Username cannot be empty'
})
if (error === 'noPassword') return res.status(401).send({
status: false,
message: 'Password field cannot be empty'
})
if (error === 'notFound') return res.status(401).send({
message: 'User is not registered'
})
if (error === 'invalidPassword') return res.status(401).send({
message: 'The password is incorrect'
})
if (error === 'badToken') return res.status(403).send({
message: 'User is not authorized'
})
return res.status(400).send(error)
}
}
sign(req, res).then((response) => console.log(response))
Hopefully, this has been helpful :) and sorry I don't use semi-colons.

Node return errors correctly (e.g. validation errors)

I'm refactoring our code into promises.
Two blocks with sample code:
user.service.js
export function updateUserProfileByUsername(req, res) {
userController.getUserByUsername(req.params.username)
.then((userProfile) => {
return userController.saveUserProfileByUser(userProfile,
req.body.email,
req.body.username,
req.body.firstname,
req.body.lastname)
})
.then((updated_user) => {
res.status(200).json(updated_user.profile);
})
.catch((err) => {
res.status(404).send('something went wrong');
});
}
export function getUserProfileByUsername(req, res) {
userController.getUserProfileByUsername(req.params.username)
.then((userProfile) => {
res.status(200).json(userProfile);
})
.catch((err) => {
res.status(404).send('something went wrong');
})
}
user.controller.js
export function getUserProfileByUsername(username) {
return User.findOne({
'username': username
}).exec()
.then((user) => {
if (user)
return user.profile;
else
throw new Error("user not found!");
});
}
export function getUserByUsername(username) {
return User.findOne({
'username': username
}).exec()
.then((user) => {
if (user)
return user;
else
throw new Error("user not found!");
});
}
export function saveUserProfileByUser(user, email, username, firstname, lastname) {
user.email = email;
user.username = username;
user.firstname = firstname;
user.lastname = lastname;
return user.save(); // returns a promise
}
Our routes enter in user/index.js, go into service.js and the controller handles our database work and errors.
What I am trying to achieve is to send fitting errors to the client.
Like: 'the user does not exist' or 'username is too long' when updating a wrong user, etc.
if I try to send the error to the client, i'll just get a empty json as result ({}). If I log the error, i get the full stack trace, including validation errors.
.catch((err) => {
console.log(err) // shows me full stacktrace of the error
res.status(404).send(err); //sends {} to the client
})
How can I implement this with promises? Should i add extra middleware sending correct error messages?
I would really appreciate some hints in the right direction for this one.
Thanks in advance!
Because err is an object I assume express is converting to JSON for you. However, when you stringify an error you get '{}';
If you want to return the stacktrace try .send(err.stack).
Alternatively if you only want the message and not the entire stack you can use err.message.
.catch((err) => {
console.log(err)
res.status(404).send(err.stack);
})

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