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

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

Related

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

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.

Cannot set headers after they are sent to the client when I try to make an update request

I am trying to create a CRUD. I have the UPDATE created, but when I try to request from postman, I get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
at ServerResponse.setHeader (_http_outgoing.js:526:11)
at ServerResponse.header (E:\freebooks-core-api\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (E:\freebooks-core-api\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:158:21)
at Object.exports.success (E:\freebooks-core-api\network\response.js:3:6)
at E:\freebooks-core-api\components\book\network.js:32:18
at processTicksAndRejections (internal/process/task_queues.js:97:5) { code:
'ERR_HTTP_HEADERS_SENT' } (node:2844)
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]:
Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:526:11)
at ServerResponse.header (E:\freebooks-core-api\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (E:\freebooks-core-api\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (E:\freebooks-core-api\node_modules\express\lib\response.js:158:21)
at Object.exports.error (E:\freebooks-core-api\network\response.js:12:6)
at E:\freebooks-core-api\components\book\network.js:34:16
at processTicksAndRejections (internal/process/task_queues.js:97:5) (node:2844)
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This
error originated either by throwing inside of an async function
without a catch block, or by rejecting a promise which was not handled
with .catch(). To terminate the node process on unhandled promise
rejection, use the CLI flag --unhandled-rejections=strict (see
https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode).
(rejection id: 1) (node:2844) [DEP0018] DeprecationWarning: Unhandled
promise rejections are deprecated. In the future, promise rejections
that are not handled will terminate the Node.js process with a
non-zero exit code.
As I read in the error, this comes from the following files:
response.js in my network folder
exports.success = function (req, res, data, status) {
res.status(status || 200)
.send({
error: '',
data
})
}
exports.error = function (req, res, error, status, log) {
console.log(log)
res.status(status || 500)
.send({
error,
data: ''
})
}
and network.js in my book (component) folder:
const router = require('express').Router()
const response = require('../../network/response')
const controller = require('./controller')
router.get('/', function (req, res) {
controller.getBooks()
.then( data => {
response.success(req, res, data, 200)
})
.catch( err => {
response.error(req, res, 'Unexpected Error', 500, err)
})
})
router.post('/', function (req, res) {
const book = req.body
console.log(book)
controller.addBook(book).then( data => {
response.success(req, res, book, 201)
})
.catch(err => {
response.error(req, res, 'Internal error', 500, err)
})
})
router.patch('/:id', function(req, res) {
const { id } = req.params
console.log(id)
const book = req.body
controller.updateBook(id, book)
.then( data => {
response.success(req, res, data, 200)
}).catch( err => {
response.error(req, res, 'Internal error', 500, err)
})
res.send('Ok')
})
module.exports = router
but since I am calling the controller and the error is running on that line, this is the code:
const store = require('./store')
function getBooks () {
return new Promise((resolve, reject) => {
resolve(store.list())
})
}
function addBook (book) {
return new Promise((resolve, reject) => {
if (!book) {
reject('I reject')
return false
} else {
resolve(book)
store.add(book)
}
})
}
function updateBook (id, book) {
return new Promise( async (resolve, reject) => {
if (!id || !book) {
reject('Invalid data')
return false
} else {
const result = await store.update(id, book)
resolve(result)
}
})
}
function deleteBook (id) {
return new Promise( async (resolve, reject) => {
if (!id) {
reject('Invalid data')
return false
} else {
const result = await store.update(id)
resolve(result)
}
})
}
module.exports = {
addBook,
getBooks,
updateBook,
deleteBook
}
finally the store
const db = require('mongoose')
const Model = require('./model')
db.Promise = global.Promise
db.connect(`mongodb+srv://fewtwtwfwe:efwefwecwecw#ferwrtervsefwg/test?retryWrites=true&w=majority`, {
useNewUrlParser: true,
useUnifiedTopology: true
}).then( () => {
console.log(`Database connected`)
}).catch( err => {
console.error(err)
})
function addBook(book) {
const myBook = new Model(book)
myBook.save()
}
async function getBooks() {
const books = await Model.find()
return books
}
async function updateBook(id, book) {
const foundBook = await Model.findOne({
'_id':id
})
foundBook.book = book
const newBook = await foundBook.save()
return newBook
}
async function deleteBook(id) {
const foundBook = await Model.findOne({
'_id':id
})
}
module.exports = {
add: addBook,
list: getBooks,
update: updateBook,
delete: deleteBook,
}
I modified the data of the connection to the database, because I am sure that the error does not lie there, I have made other requests and apparently everything is fine. Any idea?
You can only send one response for each incoming request. This particular error message tells you that your code is trying to send two requests. Most often, this occurs because of improper sequencing of code with asynchronous functions. That appears to be the case in this route:
router.patch('/:id', function(req, res) {
const { id } = req.params
console.log(id)
const book = req.body
controller.updateBook(id, book)
.then( data => {
response.success(req, res, data, 200)
}).catch( err => {
response.error(req, res, 'Internal error', 500, err)
})
res.send('Ok')
})
First it executes:
controller.updateBook()
and then while that asynchronous operation is running, it then executes:
res.send('Ok');
Then, sometime later, updateBook() finishes and it calls either the .then() or the .catch() handler and you then try to send another response to the same request.
It appears that you should just remove the res.send('Ok') entirely because you want the response to be sent according to the status of updateBook() in either the .then() or the .catch() handler.
On a completely separate topic, it's a bit of an anti-pattern to wrap a promise returning function in another promise like you are doing in updateBook(). I'd suggest you change this:
function updateBook (id, book) {
return new Promise( async (resolve, reject) => {
if (!id || !book) {
reject('Invalid data')
return false
} else {
const result = await store.update(id, book)
resolve(result)
}
})
}
to this:
function updateBook (id, book) {
if (!id || !book) {
return Promise.reject('Invalid data');
} else {
return store.update(id, book);
}
}
FYI, the reason it's an anti-pattern is that it's both unnecessary code and people often make mistakes in error handling which is exactly what you did. If store.update() rejected, you did not have a try/catch around the await to catch that error so it would have been lost and your promise would have never been resolved or rejected.
You should change all the similarly structured functions to fix this.

Async / await in node nested functions?

I'm trying to get async / await to trigger events in order, but it seems I'm missing something as my console.log markers are triggering in reverse to the order I was hoping for.
I 'm wondering if is to do with my use of nested functions in users.js but having tried multiple variations of async / await, it consistently doesn't work as expected.
// index.js
var users = require("./users.js");
app.post("/getToken", async function(req, res) {
if (req.body.email && req.body.password) {
const email = req.body.email;
const password = req.body.password;
const user = await users(email, password)
// running this should output console.log("No 1")
// from users.js first, but doesn't ?
console.log('No 2')
if (user) {
var payload = {
id: user.id
};
var token = jwt.encode(payload, cfg.jwtSecret);
res.json({
token: token
});
} else {
res.sendStatus(401);
}
} else {
res.sendStatus(401);
}
});
// users.js
module.exports = function(emailAddress, password) {
db.connect();
var query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
var query_params = [emailAddress];
db.query(
query,
query_params,
function(error, result, fields) {
console.log('No 1')
if (error) throw error;
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
return { id: result[0].id }
} else {
console.log("login False | Password");
return false;
}
} else {
console.log("login False | username");
return false;
}
}
)
}
Your users.js function doesn't return anything. The callbacks you're passing query do, but the overall function doesn't. Since it never returns anything explicitly, the result of calling it is undefined. If you await undefined, it's like await Promise.resolve(undefined) and so your resolution handler is called quite quickly.
You want that function to return a promise that doesn't get resolved until the work is done. Since what it uses is an old-style Node callbck API, it's reasonable to use new Promise to create that promise (alternately, get or create a promise-enabled API to that DB).
I also suspect you're calling connect incorrectly, since normally that would be an asynchronous action, but you're treating it as though it were synchronous.
See comments:
users.js
module.exports = function(emailAddress, password) {
return new Promise((resolve, reject) => {
// Use the callback to know when the connection is established
db.connect(error => {
if (error) {
// Connection failed
reject(error);
return;
}
var query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
var query_params = [emailAddress];
db.query(
query,
query_params,
function(error, result, fields) {
// Throwing an error here does nothing useful. Instead,
// reject the promise.
if (error) {
reject(error);
return;
}
// Resolve our promise based on what we got
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
resolve({ id: result[0].id });
} else {
console.log("login False | Password");
resolve(false);
}
} else {
console.log("login False | username");
resolve(false);
}
}
);
});
});
}
Then using it:
app.post("/getToken", async function(req, res) {
// You must handle errors, since `post` won't do anything with the return
// value of this function
try {
if (req.body.email && req.body.password) {
const email = req.body.email;
const password = req.body.password;
// Now this waits here, since `users` returns a promise that
// isn't resolved until the query completes
const user = await users(email, password)
console.log('No 2')
if (user) {
var payload = {
id: user.id
};
var token = jwt.encode(payload, cfg.jwtSecret);
res.json({
token: token
});
} else {
res.sendStatus(401);
}
} else {
res.sendStatus(401);
}
} catch (e) {
res.sendStatus(401);
}
});
The problem is that db.query function is asynchronous - you are providing callback function that is executed when database call is finished. You probably need to wrap this whole function in Promise:
module.exports = function(emailAddress, password) {
return new Promise(function(resolve, reject) {
db.connect();
var query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
var query_params = [emailAddress];
db.query(
query,
query_params,
function(error, result, fields) {
if (error) return reject(error)
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
resolve({id: result[0].id})
} else {
console.log("login False | Password");
reject();
}
} else {
console.log("login False | username");
reject();
}
}
)
})
}
You can learn more about Promise API here
EDIT:
So you should additionally make connect synchronous. Here's a piece of code I have refactored for you. It should work just fine. I have used some ES6 elements to make it more readable.
const connect = () => new Promise((resolve, reject) => {
db.connect((err) => {
if (err) return reject(err);
resolve();
})
})
const makeDbRequest = (emailAddress, password) => new Promise((resolve, reject) => {
const query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
const query_params = [emailAddress];
db.query(
query,
query_params,
handleDbData(resolve, reject, password),
);
})
const handleDbData = (resolve, reject, password) => (error, result, fields) => {
if (error) return reject(error)
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
resolve({id: result[0].id})
} else {
console.log("login False | Password");
reject();
}
} else {
console.log("login False | username");
reject();
}
}
module.exports = (emailAddress, password) => new Promise((resolve, reject) => {
connect()
.then(() => {
makeDbRequest(emailAddress, password)
.then(resolve)
.catch(reject)
})
.catch(reject);
})

Structure of multiple nested Mongoose promises

How would I structure a function that has multiple Mongoose.findOne() nested in each other?
I need to do something like
const userId = '...';
const postId = '...';
const imageId = '...';
User.findById(userId).then(user => {
if (!user) {
return res.status(400).json({
status: 'error',
err: 'User not found',
});
}
Post.findById(postId).then(post => {
if (!post) {
return res.status(400).json({
status: 'error',
err: 'Post not found',
});
}
Image.findById(imageId).then(image => {
if (!image) {
return res.status(400).json({
status: 'error',
err: 'Image not found',
});
// DO SOMETHING WITH VARIABLES 'user', 'post', AND 'image'
}).catch(err => { .. });
}).catch(err => { .. });
}).catch(err => { .. });
Since Collection.findById() returns a promise, I guess I should use chaining instead of this structure.
So it might be something like
User
.findById(userId)
.then(user => Post.findById(postId))
.then(post => Image.findById(imageId))
.then(image => {
// DO SOMETHING WITH VARIABLES 'user', 'post', AND 'image'
});
.catch(err => { .. });
but I don't know how to access the variables user, post, and image, and how to throw the errors, so I can access them in my catch statement.
Edit
I have tried this
async function getPostAsync() {
const userId = '597989c668189f31483ffdbf';
const postId = '597989c62624ea74750c74f8';
if (!userId) {
throw new Error('User id missing');
}
if (!postId) {
throw new Error('Post id missing');
}
const user = await User.findById(userId);
const post = await Post.findById(postId);
return post;
}
app.get('/', (req, res) => {
getPostAsync().then(post => {
res.json({
status: 'success',
});
}).catch(err => {
res.status(400).json({
status: 'error',
err
});
})
});
but I just receive
{
"status": "error",
"err": {}
}
Am I doing something wrong?
But I get the same result even with
async function getPostAsync() {
throw new Error('msg');
return Post.find();
}
so I might be calling the async function wrong.
You can use Promise.all:
Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
])
.then(result)=>{
let user = result[0];
let post = result[1];
let image = result[2];
})
.catch(err => { .. });
Or with destructing assignment:
Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
])
.then(([user, post, image])=>{...})
.catch(err => { .. });
You can't access those variables inside a later promise's then, but you can get round it by assigning the local resolved values to global variables
let globalUser, globalPost; // create variables for later
User
.findById(userId)
.then(user => {
globalUser = user; // assign to global
return Post.findById(postId)
})
.then(post => {
globalPost = post; // assign to global
return Image.findById(imageId)
})
.then(image => {
// DO SOMETHING WITH VARIABLES 'globalUser', 'globalPost', AND 'image'
})
.catch(err => {... });
EDIT: or when using async/await:
async function() {
const user = await User.findById(userId);
const post = await Post.findById(postId);
const image = await Image.findById(imageId);
// do something with user, post and image
}
Seeing as your promises don't rely on each other you could also use Promise.all() in an async function:
async function() {
const result = await Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
]);
const [user, post, image] = result;
// do something with user, post and image
}
EDIT 2: Error handling
async function getImage() {
let user;
try {
user = await User.findById(userId);
} catch (error) { // deal with rejection of `User.findById`
// do something with error
}
// if these fail the entire function will throw
const post = await Post.findById(postId);
const image = await Image.findById(imageId);
return image;
}
getImage()
.then(image => {... })
.catch(error => {... }); // deal with rejection of `getImage` as a whole
The above code showcases the ways you can handle errors in an async function. The first is how we deal with an error in the User.findById function, by simply wrapping it in a try catch block.
The second method is by simply letting the entire async function throw an error. I.e. if the Post.findById or Image.findById promises reject, the entire getImage() promise will reject, which you can deal with in the .catch() handler.

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