NodeJS and ExpressJS parallel processing of rest request - node.js

I am newbie with JavaScript, NodeJS and Express. I writing simple application which does the following
User makes a request.
Server makes mulitple rest calls and renders the response.
How can I make sure that all the calls are complete and I create an object that I can send to the user? I saw people said something about async.parallel. Is that the only way to go? Any examples would help.

You can use promises to run code in sequence.
Here is an example (a little scaled down) of a login functionality I made using promises.
In a module named LoginController I have placed this piece of code.
this.attemptLogin = function(body, res) {
var reason = "";
var user = null;
loginM.findUser(body.username)
.then(function(result) {
if (result.status) {
user = result.result[0];
return this.verifyPassword(body.password, result.result[0].Password);
} else {
reason = {status: false, message: "Incorrect username", result: null};
throw(reason);
}
})
.then(function(result) {
if (result.message) {
res.send({status: true, message: "Successfully logged in", result: user});
return;
} else {
reason = {status: false, message: "Incorrect password", result: null};
throw(reason);
}
}).catch(function(err) {
res.send(err);
});
}
And in another module named LoginModel (LoginM) I have placed this code
this.findUser = function(username, email) {
return new Promise(function (resolve, reject) {
pool.getConnection(function (err, connection) {
if (err) {
reject({status: false, message: err});
} else {
connection.query('select Id, Name, Email, Password from Users ' +
'where (Users.Name = ? OR Users.Email = ?) AND Removed = 0 LIMIT 1', [username, email], function (err, rows) {
connection.release();
if (!err) {
if(rows.length > 0) {
resolve({status: true, message: "Found user", result: rows});
}
else
resolve({status: false, message: null})
} else {
reject({status: false, message: err});
}
});
}
});
});
}
And a similar method for verifyPassword which also returns a promise.
Now, the things to note are that:
the code inside every then is run asynchronously
the then parts are executed in order, i.e, you won´t enter the next then until you have returned something from the previous then
whatever you resolve from the methods returning promises (findUser and verifyPassword) are passed as the variable named result in .then(function(result)

Related

'undefined' somehow breaking promise tree

I am running a Node.js/Express application. Within this code I have a function which accepts data from a 'form' to register a 'new user'. This function takes the input user information and performs a few tasks, such as checking for illegal characters, checking to determine if the input email ALREADY exists in the database, 'hashes' the input name and password, and finally writes to a (PostGres) database the 'new' user information. All this code is formatted in a 'promise tree' so each task is done sequentially, one after the other. The code is as follows:
//server.js
const db = require('./routes/queries');
const traffic = require('./routes/traffic');
...
app.post('/_register', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var newHash, newName;
var newToken = shortid.generate();
var client = req.body.user_email;
var creds = req.body.user_password;
var firstname = req.body.user_name;
db.sanitation(client, creds, firstname).then(function (direction) {
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
return db.checkEmail(client); //<==call database query here to check for existing email
}).then(function (founduser) {
if (typeof foundUser != "undefined") {
console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
if (founduser.status === "active") {res.redirect('/client_login'); }
return Promise.reject("Email EXTANT"); //break out of promise chain...to prevent additional code processing below...
} else {
console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!'); //appears in log
return traffic.hashPassword(creds); //hash password and continue processing code below...
} //'foundUser' is 'undefined'...OR NOT...
}).then(function (hashedPassword) {
console.log('PASSWORD HASHED'); //does NOT appear in logs
newHash = hashedPassword;
return traffic.hashUsername(firstname);
}).then(function (hashedName) {
console.log('NAME HASHED'); //does NOT appear in logs
newName = hashedName;
return db.createUser(newName, client, newHash, newToken);
}).then(function (data) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
},
function(error) {
console.log('USER REGISTRATION FAILURE...'); //<==THIS MESSAGE SHOWS IN 'LOGS'...WHY???
}
).then(function () {
res.redirect('/landing'); //this page re-direction DOES occur...
}).catch(function (err) {
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING...' + error);
res.redirect('/');
});
}); //POST 'register' is used to register NEW users...
Here is my issue. When this code is executed and the user email is NOT already in the database, in my logs I see the message "USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!" ...this is to be expected since the email is not in the database. From this point the code should continue to process, first 'hashing' the user password and continuing down the 'promise tree'.
In fact what does happen is that it seems the 'hashing' of the user password and name do NOT happen...since I see no log messages to indicate they executed. Instead I see the following message in the log, "USER REGISTRATION FAILURE...", which indicates a 'failure' (rejection) of the code to write to the database.
My question is WHY does the part where I check for an 'undefined' response from the "checkEmail" function NOT seem to execute my code therein (the 'return traffic.hashPassword(creds);' function) and then subsequently throw the 'reject' later in the code in the 'return db.createUser'.
This makes absolutely no sense to me. It seems as though the 'undefined' response from checking for an extant user email in the database prevents execution of parts of the remainder of the code, and inexplicably throws a 'rejection' of the database writes.
This is killing me. It has taken about a week of my time and I seem no closer to resolving this issue. If my code to handle the 'undefined' return from the 'checkEmail' call is somehow incorrect can somebody demonstrate a proper way to perform this? Any advice is HUGELY appreciated.
I have made comment notations in my code above to illustrate what is and what is not displaying in my logs
UPDATE:
Based upon the kind feedback I have received, I have re-written the code above using two different approaches. Here is the first:
app.post('/register', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var newHash, newName;
var client = req.body.client_email;
var creds = req.body.client_password;
var newToken = shortid.generate();
var firstname = req.body.client_name;
try {
const users = db.checkEmail(client);
users.then(function(result) {
console.log('FINAL RESULT ROWS ARE: ' + result.rows)
if (typeof result.rows != "undefined") {
console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!');
if (result.status === "active") {
console.log("Email EXTANT");
return res.redirect("/client_login");
} //"active"
} else {
console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...');
return traffic.hashPassword(creds);
} //'result.rows' is 'undefined'...OR NOT...
})
.then(function(result) {
console.log('PASSWORD HASHED');
console.log(result);
newHash = result;
return traffic.hashUsername(firstname);
})
.then(function(result) {
console.log('NAME HASHED');
newName = result;
return db.createUser(newName, client, newHash, newToken);
})
.then(function(result) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
})
.then(function(result) {
res.redirect('/landing'); //route to 'landing' page...
});
} catch(err) {
// handle errors
console.log('ERROR IN TRY/CATCH IS: ' + err);
}
}); //POST 'register' is used to register NEW clients...
This code is functional, however it always reports the 'email' is NOT being in the database...even when in fact it is. Here is the log of the output:
FINAL RESULT ROWS ARE: undefined
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...
PASSWORD HASHED
$2b$10$vW3.YkPyoB9MG5k9qiGreOQC05rWsEIO6i.NkYg6oFqJ8byNjp.iu
NAME HASHED
REGISTERED A NEW CLIENT JOIN...!!!
Here is the second block of code, using an 'async/await' in the function:
app.post('/register', async function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var newHash, newName;
var client = req.body.client_email;
var creds = req.body.client_password;
var newToken = shortid.generate();
var firstname = req.body.client_name;
try {
//const direction = await db.sanitation(client, creds, firstname);
const founduser = await db.checkEmail(client);
console.log('founduser ROWS ARE: ' + founduser.rows)
if (typeof foundUser != "undefined") {
console.log("HEY THERE IS ALREADY A USER WITH THAT EMAIL!", foundUser);
if (founduser.status === "active") {
console.log("Email EXTANT");
return res.redirect("/client_login");
}
} //NOT "undefined"
console.log("USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...!!!");
} catch (err) {
console.log("THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE TRY STATEMENT..." + err);
return res.redirect("/");
}
}); //POST 'register' is used to register NEW clients...
This code is ALSO functional, however as with the first block of code it always reports the 'email' is NOT being in the database...even when in fact it is. Here is the log of the output:
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...!!!
Based upon these results, it is my belief either block of code is likely functional...and the reason all executes report the email as 'undefined' (even when it already exists in the database) is because of the "checkEmail" function. I probably have it incorrectly written to properly return an 'async' result. Here is that code:
const Pool = require('pg').Pool;
const pool = new Pool({
user: 'postgres',
host: '127.0.0.1',
database: 'myDB',
password: 'password',
})
const checkEmail = async function(mail) {
return new Promise(function(resolve, reject) {
pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) {
if (error) {
reject(new Error('Error processing a database check for email!'));
} else {
resolve(results.rows);
}
console.log('checkEmail mail: ' + mail);
console.log('checkEmail results.rows: ' + results.rows);
}) //pool.query
}); //new promise
}
Is somebody able to confirm my suspicion that BOTH of the blocks of "try/catch" code above are written correctly...and the problem with the call always returning "undefined" lies in the "checkEmail" function? And, if that is the case...perhaps suggest how I need to correct that "checkEmail" function to properly find the existing email in the database when necessary. I am not terribly familiar with usage of 'async' functions and have never attempted their usage in a promise to query a database. I thank you in advance for any reply.
UPDATE/SOLUTION:
When I first wrote the "checkEmail" promise function, I assumed it would 'resolve' if a matching email was discovered in the database...and 'reject' if it was not. What I am experiencing is that the function always 'resolves', even if the email is not located in the database. Therefore I found usage of the 'object.keys' method to be useful to check if in fact some data returned from the function. Using this I can write code that now seems to be properly functioning. Here is my current "checkEmail" function:
//queries.js
const checkEmail = async function(mail) {
return new Promise(function(resolve, reject) {
pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) {
if (error) {
reject(new Error('Error processing a database check for email!'));
} else {
resolve(results.rows);
}
console.log('checkEmail mail: ' + mail);
console.log('checkEmail results.rows: ' + results.rows);
}) //pool.query
}); //new promise
}
module.exports = {
...
checkEmail,
...
}
and my promise tree:
//server.js
app.post('/register', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var client = req.body.client_email;
var creds = req.body.client_password;
var newToken = shortid.generate();
var firstname = req.body.client_name;
db.sanitation(client, creds, firstname)
.then(function (direction) {
console.log('Result direction Object.keys from SANITATION: ', Object.keys(direction).length);
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
return db.checkEmail(client); // <==call database query here to check for existing email for existing email
})
.then(function (founduser) {
console.log('foundUser matching email in database: ', founduser);
console.log('foundUser Object.keys matching email in database: ', Object.keys(founduser).length);
if (Object.keys(founduser).length > 0) {
console.log('EMAIL IS EXTANT IN DATABASE ALREADY!');
if (founduser.length) {console.log('foundUser LENGTH matching email in database: ', founduser.length);}
if (founduser[0].status === 'active') {
console.log('USER-SUPPLIED EMAIL EQUALS THAT OF AN ACTIVE USER');
throw new Error('active'); //break out of promise chain...to prevent additional code processing below...
} else {
console.log('USER-SUPPLIED EMAIL APPEARS IN THE DATABASE');
throw new Error('Email EXTANT'); //break out of promise chain...to prevent additional code processing below...
} //founduser[0].status
} //founduser.length EXCEEDS "0"
if (Object.keys(founduser).length === 0) {
console.log('EMAIL IS NOT PRESENT IN THE DATABASE!');
return traffic.hashPassword(creds); // hash password and continue processing code below...
} //founduser.length EQUALS "0"
})
.then(function (hashedPassword) {
console.log('PASSWORD HASHED');
return traffic.hashUsername(firstname)
.then(function (hashedName) { // nested to keep hashedPassword within scope
console.log('NAME HASHED');
return db.createUser(hashedName, client, hashedPassword, newToken)
.catch(function (error) { // nested in order to catch only an error arising from db.createUser(), (not necessary other than to log out an error message).
console.log('USER REGISTRATION FAILURE...'); // this message will appear only if db.createUser() fails
throw error; // RETHROW error in order to jump to the terminal catch (and hit the `default` case).
});
});
})
.then(function (data) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
res.redirect('/landing'); // success
})
.catch(function (err) {
switch(err.message) {
case 'active':
res.redirect('/client_login');
break;
case 'Email EXTANT':
res.redirect('/client_login');
break;
default: // all unexpected errors
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING... ' + err.message);
res.redirect('/');
}
});
}); //POST 'register' is used to register NEW clients...
I would like to thank those individuals that responded to this post. I am greatly appreciative of their time and suggestions that allowed me to get to this point of what is now evidently functional code. Those replies, in addition, are very instructive and I have learned some new techniques from the help I have received.
I wonder if you might be missing one or more of the following basic principles governing errors in promise chains:
if an Error is caught and you want it not to be marked as handled (eg if you catch an Error just to log it) then you must rethrow the error (or throw an Error of your own) in order to proceed down the promise chain's error path.
if an Error is caught and not rethrow then promise chain will proceed down its success path. If a value is not explicitly returned, then undefined will be delivered to the next step.
a naturally occuring or deliberately thrown Error will propagate to the next qualifying .catch().
a .catch() in a given chain will catch any earlier error, not just one arising from the immediately preceeding step.
a .catch() written in the form .then(successHander, errorHandler) will catch errors from preceding steps in the chain but not from the successHander. This can be useful (but not here).
a .catch() can often be made "private" (ie specific to a particular async step) by nesting it within the main chain. This avoids catching errors arising from earlier in the chain.
within a promise chain throwing an error is more economical than return Promise.reject(...).
You can embed the redirects in the chain however I suggest that it's cleaner to throw errors and branch in the terminal .catch() (eg with a switch/case structure).
You might end up with something like this (plenty of comments in code) ...
//server.js
const db = require('./routes/queries');
const traffic = require('./routes/traffic');
...
app.post('/_register', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
// var newHash, newName; // not needed
var newToken = shortid.generate();
var client = req.body.user_email;
var creds = req.body.user_password;
var firstname = req.body.user_name;
db.sanitation(client, creds, firstname)
.then(function (direction) {
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
return db.checkEmail(client); // <==call database query here to check for existing email
})
.then(function (founduser) {
if (typeof foundUser != "undefined") { // not a particularly good test, maybe if(foundUser) {...} would be better?
console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
if (founduser.status === 'active') {
throw new Error('active'); // break out of promise chain...to prevent additional code processing below...
} else {
throw new Error('Email EXTANT'); // break out of promise chain...to prevent additional code processing below...
}
} else {
console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!'); // appears in log
return traffic.hashPassword(creds); // hash password and continue processing code below...
}
})
.then(function (hashedPassword) {
console.log('PASSWORD HASHED');
return traffic.hashUsername(firstname)
.then(function (hashedName) { // nested to keep hashedPassword within scope
console.log('NAME HASHED');
return db.createUser(hashedName, client, hashedPassword, newToken)
.catch(function (error) { // nested in order to catch only an error arising from db.createUser(), (not necessary other than to log out an error message).
console.log('USER REGISTRATION FAILURE...'); // this message will appear only if db.createUser() fails
throw error; // RETHROW error in order to jump to the terminal catch (and hit the `default` case).
});
});
})
.then(function (data) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
res.redirect('/landing'); // success
})
.catch(function (err) {
// Suggest you perform all error case redirects here, depending on which error occurred.
// May not be 100% correct but you get the idea.
switch(err.message) {
case 'active':
res.redirect('/client_login');
break;
case 'Email EXTANT':
default: // all unexpected errors
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING... ' + err.message);
res.redirect('/');
}
});
}); // POST 'register' is used to register NEW users...
I think the problem is return Promise.reject("Email EXTANT");. If you want to break the execution, you can just use return res instead.
Try the example below with asyn/await approach.
Edit: add checkEmail updates
//server.js
const db = require("./routes/queries");
const traffic = require("./routes/traffic");
app.post("/_register", async function (req, res) {
if (!req.body) {
console.log("ERROR: req.body has NOT been returned...");
return res.sendStatus(400);
}
var newToken = shortid.generate();
var client = req.body.user_email;
var creds = req.body.user_password;
var firstname = req.body.user_name;
try {
const direction = await db.sanitation(client, creds, firstname);
const foundusers = await db.checkEmail(client);
if (foundusers.length) {
console.log(
"HEY THERE IS ALREADY A USER WITH THAT EMAIL!",
foundusers[0]
);
if (foundusers[0].status === "active") {
console.log("Email EXTANT");
return res.redirect("/client_login");
}
}
console.log(
"USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!"
);
const hashedPassword = await traffic.hashPassword(creds);
console.log("PASSWORD HASHED");
const hashedName = await traffic.hashUsername(firstname);
await db.createUser(hashedName, client, hashedPassword, newToken);
console.log("REGISTERED A NEW CLIENT JOIN...!!!");
return res.redirect("/landing");
} catch (err) {
console.log("THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING..." + err);
return res.redirect("/");
}
});
I update the checkEmail function.
Reminder: you should create a db.js to export the pool instead of create a pool inside the checkEmail.js file. Then when you need to query in other function, they can import the pool from it instead of recreateing a new pool.
const Pool = require("pg").Pool;
const pool = new Pool({
user: "postgres",
host: "127.0.0.1",
database: "myDB",
password: "password",
});
export const checkEmail = async function (mail) {
try {
const res = await pool.query("SELECT * FROM clients WHERE email = $1", [
mail,
]);
console.log(res);
return res.rows;
} catch (err) {
throw err;
}
};

check if a user already exists in database node js using couchbase

I am trying to check if a user already exists in the database, I have managed to stop creating a user if one already exists with the same phone number , however I do not seem to get the error message displayed. I am not too sure why my error is not being handled correctly. Here is my code:
exports.usercreate = function (req, res)
{
users.create(req.body, function (err, result)
{
var phonenumber = req.body.phonenumber;
console.log(phonenumber);
if (phonenumber.length > 0)
{
res.status(200).json(
{
status: "error",
resCode: 400,
msg: 'cutomer added error'
});
}
else
{
res.status(200).json(
{
status: "success",
resCode: 200,
msg: "users Added Successfully",
});
}
else
{
console.log(error)
}
});
};
Getting error like customer added error. but records are inserted in couchbase
as #TommyBs mentioned, you are basically comparing an N1qlQuery object to whatever is coming on req.body.phonenumber
...
bucket.query(query, function(err, rows, meta) {
for (row in rows) {
if(row.phonenumber == req.body.phonenumber) {
res.status(500).json({status:"error", resCode: 500, msg:"users Already exist"});
}
}
}

How to exit after response in nodejs with express?

This is my first time asking a question on stackoverflow. Sorry if I made posting mistakes.
I am trying to exit a function after sending a response to prevent continuing through the function.
node -v = v12.6.0
express = ^4.17.1
mongoose = ^5.6.6
// handle adding a new book request submission
addNewBook: function (req, res) {
var response = null;
var name = req.body.name.toLowerCase();
var url = req.body.url.toLowerCase();
var category = req.body.category.toLowerCase();
var tags = req.body.tags.toLowerCase();
// checking if category already exist. if not, make a new category
Category.find({label: category}).exec(function(err, data) {
if(err) {
response = res.status(400).send({message:'Error finding category.'});
} else if(data.length === 0) {
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
response = res.status(400).send({message:'Error saving new category.'});
}
})
}
});
// checking if book name already exist
Book.find({name: name}).exec(function(err, data){
if(err) {
response = res.status(400).send({message:'Error validating Book existence'});
} else if(data.length > 0) {
response = res.status(200).send({message:'book name already exist'});
} else {
req.body.name = name;
req.body.url = url;
req.body.category = category;
req.body.tags = tags;
// make a new book document
var newBook = new Book(req.body);
newBook.save(function (err, data) {
if (err) {
response = res.status(400).send({message: 'Error saving new Book.'});
} else {
response = res.json(data);
}
})
}
});
return response;
},
Function continues to executes other part of the function code after a return.
I am also getting "Cannot set headers after they are sent to the client" error on node. Im guessing, preventing the function to continue after sending a response will fix this as well?
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
There are two problems with the flow of your logic. First is that return only returns a function. It does not return the function that calls a function or the function that defines a function.
Basically, your code is:
Category.find({label: category}).exec(function(err, data) {
if(err) {
// ...
return;
} else if(/* ... */) {
// ...
newCategory.save(function(err, data){
if(err) {
// ...
return;
}
})
}
});
moreStuffDownHere();
// ...
Let's rewrite that to not use anonymous functions to make it clear what's really happening
function findCallback (err, data) {
if(err) {
// ...
return; // it's obvious that this returns form findCallback()
// and NOT yourFunction()
} else if(/* ... */) {
// ...
newCategory.save(saveCallback);
}
}
function saveCallback (err, data) {
if(err) {
// ...
return;
}
}
function yourFunction () {
Category.find({label: category}).exec(findCallback);
moreStuffDownHere();
}
So you can now see that you are not calling return anywhere in yourFunction().
The second problem is that Category.find().exec() is asynchronous. This means it returns immediately and let any code below it run before calling findCallback(). To solve the async issue just move moreStuffDownHere() inside findCallback().
Therefore, the simplest change to get your program flow working is to move moreStuffDownHere:
Category.find({label: category}).exec(function(err, data) {
if(err) {
res.status(400).send({message: 'Error finding category.'});
return;
} else if(data.length === 0) {
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
res.status(400).send({message: 'Error saving new category.'});
return;
}
// More stuff down here, that now will only execute if there are no errors
})
}
});
Improve program flow
One issue I have with the solution above is that now moreStuffDownHere is hardcoded inside the save callback. One way around it is to refactor the entire operation and make it your own internal API:
function addNewCategory (category, callback) {
// callback will be passed status depending on success or failure
Category.find({label: category}).exec(function(err, data) {
if(err) {
// ...
callback('FIND_FAILURE');
return;
} else if(/* ... */) {
// ...
newCategory.save(function(err, data){
if(err) {
// ...
callback('SAVE_FAILURE');
return;
}
callback('OK');
})
}
});
}
Now inside yourFunction() you can check the result of the entire operation and decide to return or continue:
function yourFunction() {
// ...
addNewCategory(category, function (status) {
switch (status) {
case 'FIND_FAILURE':
res.status(400).send({message: 'Error finding category.'});
return;
case 'SAVE_FAILURE':
res.status(400).send({message: 'Error saving new category.'});
return;
}
// More stuff down here ...
});
}
Improvement 2 - Promises
It's possible to make the program flow much easier to read by using Promises along with async/await. For that you need to wrap the operation in a promise. We can use the addNewCategory function we wrote above as an example:
function addNewCategory (category) {
// returns a Promise of true/false
return new Promise(function (resolve, reject) {
Category.find({label: category}).exec(function(err, data) {
if(err) {
// ...
resolve('FIND_FAILURE'); // you can also use reject if you want
// to use a try/catch flow
return;
} else if(/* ... */) {
// ...
newCategory.save(function(err, data){
if(err) {
// ...
resolve('SAVE_FAILURE');
return;
}
resolve('OK');
})
}
});
});
}
Now the code is slightly easier to follow because it allows you to keep moreStuffDownHere where you originally have it without moving it inside another function:
async function yourFunction() {
// ...
var status = await addNewCategory(category);
switch (status) {
case 'FIND_FAILURE':
res.status(400).send({message: 'Error finding category.'});
return;
case 'SAVE_FAILURE':
res.status(400).send({message: 'Error saving new category.'});
return;
}
// More stuff down here ...
}
Note: Express accepts functions marked as async as routes/middlewares. You just need to call res.send() or next() as usual
The error is as a result of your condition. Hence, both code blocks are run resulting in the response being sent twice. To fix this change your code to this below.
Category.find({label: category}).exec(function(err, data) {
if(err) {
res.status(400).send({message: 'Error finding category.'});
} else if(data.length>0) {
//there is no data with that label - Hence, create one
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
//if error
res.status(400).send({message: 'Error saving new category.'});
}else{
//if item saves
res.status(200).send({message: 'Item saved'});
}
})
}else{
//there is a data with that label availble - do something else
res.status(200).send(data)
}
});
The error you report happens when there are code paths that can send a response more than once. You get one and only one response per request. So, calling res.send() more than once is one way that you get that error.
Preventing this when you have a number of asynchronous operations requires a bit more work as you have to make sure that all your code is properly sequenced and all error paths are properly terminated (so further processing doesn't happen). In general, this code is a lot easier to write using promise-based interfaces for your asynchronous operations, but since you aren't using the promise interface on your database, I'll show how you can do it with your existing callback interface. In generally, it involves a lot of nesting inside of asynchronous callbacks and very careful if/else and return around conditionals and errors.
Your code is subject to this error because you are running Category.find() and Book.find() in parallel. You don't wait for the Category.find() code to finish before doing the book operations. If the category code causes an error, you will send that error response, but still continue with the book code which will then send its response. Instead, you need to make sure that if there's an error with the category stuff that you don't run the book code at all. For the plain callback interface on your database, that means nesting the book code inside a callback from the category code. To make this simpler to write, I put the category code into it's own function that has one completion callback that we can use to know when its all done.
Here's one way to do it:
// utility function to create category if needed, requires callback
// to communicate results
function createCategoryIfNeeded(category, fn) {
// checking if category already exist. if not, make a new category
Category.find({label: category}).exec(function(err, data) {
if(err) {
fn({message:'Error finding category.'});
} else if(data.length === 0) {
let newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if (err) {
fn({message:'Error saving new category.'});
} else {
// category created
fn(null, true);
}
})
} else {
// category already exists
fn(null, false);
}
});
}
// handle adding a new book request submission
addNewBook: function (req, res) {
var name = req.body.name.toLowerCase();
var url = req.body.url.toLowerCase();
var category = req.body.category.toLowerCase();
var tags = req.body.tags.toLowerCase();
createCategoryIfNeeded(category, function(err, created) {
if (err) {
res.status(400).send(err);
} else {
// checking if book name already exist
Book.find({name: name}).exec(function(err, data){
if(err) {
res.status(400).send({message:'Error validating Book existence'});
} else if(data.length > 0) {
res.status(200).send({message:'book name already exist'});
} else {
req.body.name = name;
req.body.url = url;
req.body.category = category;
req.body.tags = tags;
// make a new book document
var newBook = new Book(req.body);
newBook.save(function (err, data) {
if (err) {
res.status(400).send({message: 'Error saving new Book.'});
} else {
res.json(data);
}
});
}
});
}
});
},
The error meassage says that, res can be send once it has been send. So returning it along with the response.
Category.find({label: category}).exec(function(err, data) {
if(err) {
return res.status(400).send({message: 'Error finding category.'});
} else if(!data) {
var newCategory = new Category({label: category, description: '', keywords: ''});
newCategory.save(function(err, data){
if(err) {
return res.status(400).send({message: 'Error saving new category.'});
}
})
}
});

result object returned as undefined when assigned a value

I am creating a results object to return a boolean and a string. it is not being reassigned within the if statement. The rest of the code is working correctly and the password gets updated or the right error message is output to the console
i've tried leaving result undefined before the try catch.
async function passwordUpdate(password, currentPassword, newPwd, email) {
let hashedPassword = await bcrypt.hash(newPwd, 10);
let result = { success: false , message: ' '};
try {
bcrypt.compare(currentPassword, password, async function (err, res) {
if (res) {
let updateResult = await updatePwd(hashedPassword, email);
if (updateResult) {
result = { success: true , message: 'Password was updated successfully.'};
}
else {
logger.info('Password was not updated successfully.');
}
} else {
logger.error('Passwords do not match');
result= { success: false , message: 'Your current password was entered incorrectly'};
logger.error(result.message);
}
});
} catch (error) {
result= { success: false , message: 'Failed to compare passwords'}
}
logger.error('result ', result.message);
return result;
}
code is being called by this method
app.post('/passwordUpdate', async (req, res) => {
let pwd = req.body.password;
let cpwd = req.body.currentPwd;
let newPwd = req.body.newPwd;
let email = req.body.email;
try {
let result = await usersModel.passwordUpdate(pwd, cpwd, newPwd, email);
console.log(result, result.success, result.message);
if (result.success) {
res.status(200).json({error: result.message});
}
else {
res.status(404).json({error: result.message});
}
} catch (error) {
console.log(error);
}
});
logger.error(result.message); this line within the else statement is outputting the message as expected but
logger.error('result ', result.message); after the try/catch is outputting a blank message for result.message
When you're doing this:
let result = await usersModel.passwordUpdate(pwd, cpwd, newPwd, email);
The passwordUpdate function is resolving the promise based on what is in that function's "top level". This means that the callback function of bcrypt.compare doesn't affect the return of passwordUpdate which is why you're not seeing the result you're looking for.
What you can do is wrap the entire thing in a promise and call the resolve/reject inside the bcrypt.compare function.
async function passwordUpdate(password, currentPassword, newPwd, email) {
return new Promise(async function(resolve, reject) {
let hashedPassword = await bcrypt.hash(newPwd, 10)
try {
bcrypt.compare(currentPassword, password, async function(err, res) {
if (res) {
let updateResult = await updatePwd(hashedPassword, email)
if (updateResult) {
resolve({
success: true,
message: 'Password was updated successfully.',
})
} else {
reject({
success: false,
message: 'Password was not updated successfully.',
})
}
} else {
reject({
success: false,
message: 'Your current password was entered incorrectly',
})
}
})
} catch (error) {
reject({ success: false, message: 'Failed to compare passwords' })
}
})
}
You'll also notice I removed the logging, you can put these back if you wish but since this is a promise you can log your errors more centrally from the caller in the .then and .catch or try/catch if you're using async/await.
Also may I suggest that since you'll be able to determine whether it's an error or not based on the resolve and reject, that you can remove the success from the result and only return a string, it'll make the code cleaner:
async function passwordUpdate(password, currentPassword, newPwd, email) {
return new Promise(async function(resolve, reject) {
const hashedPassword = await bcrypt.hash(newPwd, 10)
try {
bcrypt.compare(currentPassword, password, async function(err, res) {
if (res) {
const updateResult = await updatePwd(hashedPassword, email)
if (updateResult) {
resolve('Password was updated successfully.')
} else {
reject('Password was not updated successfully.')
}
} else {
reject('Your current password was entered incorrectly')
}
})
} catch (error) {
reject('Failed to compare passwords')
}
})
}
PS: I didn't test the code pasted above, I only modified your code to better explain it.

A way to prevent highly nested code in Sequelize for error handling?

I have a form and when something doesn't go right after submission, I want to be able to give the user specific error messages. I am using Sequelize as my ORM, and the promises returned are getting a bit messy with all the nested code.
For instance, if we have two models to update: User and Photo:
models.User.find({where: {name: 'bob' }}).then(function(user) {
if(user) {
user.updateAttributes({email: email}).then(function(user) {
models.Photo.find({where: { hash: hash}}).then(function(photo) {
if(photo)
res.json({"message":"updated"});
else
res.status(404).json({"error": "Could not find Photo"});
}).catch(err) {
res.status(500).json({"error": err});
});
}).catch(function(err) {
res.status(500).json({"error": err});
});
} else {
res.status(404).json({"error": "Could not find user"});
}
}).catch(function(err) {
res.status(500).json({"error": err});
});
And if I have 10 fields in the form to update, all the nested code can become overbearing.
What recommendations can be given if I wish to have specific error descriptions, but also more readable code? Would it be possible to capture all the 404 and 500 errors in one code block instead of breaking them up like I have?
You can make use of promise chaining and a helper method to reduce your code to a series of 3 .thens and a single .catch:
function notFoundError(model) {
var e = new Error('Could not find ' + model);
e.statusCode = 404;
throw e;
}
models.User
.find({where: {name: 'bob'}})
.then(function (user) {
if (user) {
return user.updateAttributes({email: email});
} else {
notFoundError('user');
}
})
.then(function (user) {
return models.photos.find({where: {hash: hash}});
})
.then(function (photo) {
if (photo)
res.json({"message": "updated"});
else
notFoundError('photo');
})
.catch(function (err) {
if (err.statusCode) {
res.status(err.statusCode);
} else {
res.status(500);
}
res.json({'error': err.message});
});

Resources