How to make some code wait for a function to complete? - javascript

I have a node server, this is the login route. I am trying to send a response after checking if the user exists in the DB and if the password match the hash value. I am using a custom flag but the code where I check for the value is executed before the value can be changed.
router.route('/login').post( (req,res) => {
User.findOne({email: req.body.email} ,(err,user) => {
let check = false
if(!user){
return res.json({
loginSuccess:false,
message : "Invalid Email or Password"
})
}
user.comparePassword(req.body.password,(err, isMatch)=> {
if(err){
return res.json({
loginSuccess : false,
message : "Can't Login. Try Again!"
})
};
if(isMatch){
check = true
}
})
if(!check){
return res.json({
loginSuccess : false,
message : "Invalid Eail or Password"
})
}
user.generateToken((err, user) => {
if(err) {
return res.status(400).send(err)
}
else{
res.cookie('x_auth' , user.token)
.status(200)
.json({
user : user,
loginSuccess:true
})
}
})
})
})
In the code above, how can I make
if(!check){
return res.json({
loginSuccess : false,
message : "Invalid Eail or Password"
})
}
Wait for this:
user.comparePassword(req.body.password,(err, isMatch)=> {
if(err){
return res.json({
loginSuccess : false,
message : "Can't Login. Try Again!"
})
};
if(isMatch){
check = true
}
})
Actually the code below executes first before the flag is changed, but I want it to wait, so it can decide on the new value of check

You want to do your checking inside the callback.
router.route('/login').post((req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "Invalid Email or Password"
})
}
user.comparePassword(req.body.password, (err, isMatch) => {
if (err) {
return res.json({
loginSuccess: false,
message: "Can't Login. Try Again!"
})
};
if (!isMatch) {
return res.json({
loginSuccess: false,
message: "Invalid Eail or Password"
})
}
user.generateToken((err, user) => {
if (err) {
return res.status(400).send(err)
}
else {
res.cookie('x_auth', user.token)
.status(200)
.json({
user: user,
loginSuccess: true
})
}
})
})
})
})

You need to convert that callback into promise:
await new Promise((resolve, reject) => {
user.comparePassword(req.body.password, (err, isMatch)=> {
if(err){
return res.json({
loginSuccess : false,
message : "Can't Login. Try Again!"
});
};
if(isMatch){
check = true;
resolve();
}
});
});

You can use Javascript's Promise API, where the executor function would execute your login and the .then and .catch would be executed after the promise's executor, depending on whether it was fulfilled. The documentation provides this example:
let myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR or an HTML5 API.
setTimeout( function() {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log("Yay! " + successMessage)
});
Basically you will need to resolve/reject the promise at the end of your login method. You can also resolve this with callbacks, as Duc Nguyen has already suggested, but in that case beware callback hell.

Related

Stop execution of code after returning response in Node JS?

I am new to Node JS. I am practising to build a MERN app. I have two function getUserById and isSignedIn. These two function are middlewares.
router.param("userId",getUserById)
const getUserById = (req,res,next,id)=>{
User.findById(id).exec((err,user)=>{
if(err){
return res.json({
error:"Unable to process request",
status: false,
})
}
console.log(1234);
if(!user){
return res.json({
error:"User doesn't exist",
status: false
})
}
req.user=user;
next()
})
}
const isSignedIn = (req,res,next)=>{
const token = req.headers.authorization.split(" ")[1]
jwt.verify(token, process.env.SECRET_KEY, (err, decoded)=>{
if(err){
return res.json({
status: false,
error: "Invalid Token"
})
}
console.log(123);
req.auth=decoded
next()
})
};
router.post("/api/create/:userId",isSignedIn,(req,res)=>{ res.send("Success")})
This is my understanding. If in url userId is found getUserById will be executed and then isSigned in. In getUserById if there was an error or if the user doesn't exist if will send a response and the execution of code stop there. But in isSignedin if the token is not valid I am sending a response as Invalid Token and the code execution should stop there. But the code after if is also getting executed why it is so?
Try following code
router.param("userId",getUserById)
const getUserById = async (req,res,next,id)=>{
try {
const user = await User.findById(id)
if(!user){
return res.json({
error:"User doesn't exist",
status: false
})
} else {
req.user=user;
next()
}
} catch (err) {
return res.json({
error:"Unable to process request",
status: false,
error: JSON.stringify(err),
})
}
}
const isSignedIn = async (req,res,next)=>{
const token = req.headers.authorization.split(" ")[1]
try {
const decoded = await jwt.verify(token, process.env.SECRET_KEY)
if (decoded) {
req.auth=decoded
next()
}
} catch (err) {
if(err){
return res.json({
status: false,
error: "Invalid Token"
})
}
}
};
router.post("/api/create/:userId",isSignedIn,(req,res)=>{ res.send("Success")})

How to set status code to 404 whenever a promise is rejected

I am verifying login functionality by using promises and chaining them. When user enters invalid password, rejecting the data or else resolving it. At the end i am verifying if user is logged in successfully by chaining these methods.
let verifyData = (req, res) => {
return new Promise((resolve, reject) => {
if (req.body.name) {
userModel.findOne({ name: req.body.name }).exec((err, result) => {
if (result) {
resolve(result);
} else {
reject(err);
}
});
} else {
let apiResponse = response.generate(
false,
null,
404,
"Mandatory fields missing, Please provide your userId and password"
);
reject(apiResponse);
}
});
};
Is there a way i can send status code as 404 whenever data is rejected?
This is causing an issue as currently i am getting 200 status code for rejected data
verifyData(req, res).then(validatePassword).then((result) => {res.send(result);})
.catch((err) => {
res.send(err);
});
see: https://expressjs.com/en/api.html#res.status
verifyData(req, res).then(validatePassword).then((result) => {res.send(result);})
.catch((err) => {
const status = err.STATUS_PROP; // get status from apiResponse
// or
const status = 400
res.status(status).send(err);
});

Why is my promise not resolving correctly?

exports.addUser = async(req, res) => {
const {
username,
email,
password
} = req.body;
//hash password
const password_hash = await hashPassword(password);
//check whitelist
this.checkWhitelist(email).then(function(response) {
if (response) {
console.log("RESOLVED TRUE")
//POST user to Airtable
new Promise(function(resolve, reject) {
return usersTable.create({
email,
username,
password_hash,
"email_verified": "false"
},
function(err) {
if (err) {
resolve(false);
console.error(err);
res.send({
"Success": false,
"responseCode": 502,
})
}
resolve(true);
res.send({
"Success": true,
"responseCode": 200,
});
}
).then(function(response) {
if (response) {
const EMAIL_SECRET = "xxxxxxxxxxx";
jwt.sign({
'username': username,
},
EMAIL_SECRET, {
expiresIn: '1d',
},
(err, emailToken) => {
const url = `http://localhost:3000/confirmation/${emailToken}`;
transporter.sendMail({
to: args.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`,
});
}
)
}
})
})
} else {
console.log('RESOLVED FALSE')
res.send({
"Success": false,
"responseCode": 403
})
}
})
}
For some reason, the promise I created at usersTable.create is not resolving correctly. When I call .then() after, I get the error: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'then' of undefined.
For context, this is the user registration flow for a webapp. First, the pass is hashed, then the email is check against a whitelist (so far this logic is working). Now I just need to verify the email, but can't get the .then() to call correctly.
What's going on?
In your first then where you return createTable you'll need to return new Promise so it can be chained to the next then.
If createTable returns a promise, you can simply write return createTable and get rid of the new Promise that wraps it.
Since you are using async-await earlier in the code, I'd recommend you moving to that completely as it makes the code much easier to read.
I took a stab at that,
exports.addUser = async(req, res) => {
const {
username,
email,
password
} = req.body;
//hash password
const password_hash = await hashPassword(password);
//check whitelist
try {
const whitelist = await this.checkWhiteList(email)
if (whitelist) {
await usersTable.create() // shortened for readability sake.
const EMAIL_SECRET = 'xxxxxxxxxxx';
jwt.sign(
{
'username': username,
},
EMAIL_SECRET,
{
expiresIn: '1d',
},
(err, emailToken) => {
const url = `http://localhost:3000/confirmation/${emailToken}`;
transporter.sendMail({
to: args.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`,
});
}
);
}
res.send({
'Success': true,
'responseCode': 200,
});
} catch (error) {
res.send({
'Success': false,
'responseCode': 403,
});
}
}

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.

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