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;
}
};
Related
I am part of a project which uses nodeJS + ExpressJS for the backend application, and We have a middleware function to log accesses on routes in the database.
When an User tries to access the /user route with a post method, a middleware receives the Request, get information like the URL, ip address, origin, a description of the event and record it in the database.
Everything works just fine, but some of my teammates were discussing about how to log the erros also in the database.
I will put bellow a code example
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
response.status(500).json({ error });
}
};
When we were discussing about how to implement it, we realized we'd have to call a log error function in a lot of places since we have many flows which leads to an error response.
So the code would be just like:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
function() // here we would log the error
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // here we would log the error
response.status(500).json({ error });
}
};
is it a properly way of dealing with error logging or is there any better way of doing it? Thank you for reading!
You can use the built-in error handler provided by Express.JS for this kind of logic, of course it requires a bit of setup. Like most things in Express.JS, the error handler it's just a middleware function with four parameters err, req, res and next, which MUST be placed after all your other middlewares. It comes to play when, inside a router handle (for example), your call next(err) (where err it's an Error) or by simply throwing err. Check out the documentation for more.
app.use(...)
app.use(...)
app.use((req, res, next) => {
if (req.params.id === undefined) {
let error = new Error("ID required.")
error.statusCode = 400
error.statusMessage = "Request not valid, ID not found."
throw error;
} else {
// Do some stuff...
}
})
// NOTE: After ALL your other middlewares
app.use((err, req, res, next) => {
console.error(err)
res
.status(err.statusCode)
.json(err.statusMessage)
})
Ideally you should log the errors only inside the catch block. Whenever you encounter an error just throw a new error by calling throw new Error("Type your error message here"). Then your function inside catch block will log and handle the error appropriately.
I would change your code to this:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
throw new Error("E-mail already in use")
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // log your error
response.status(500).json({ error.message });
}
};
Read more about Errors here.
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.'});
}
})
}
});
I am using Helpers in Sails for login.
This is my helpers/login.js:
fn: async function (inputs, exits) {
const password = inputs.password;
const email = inputs.email;
// find user with email
const user = await User.findOne({email});
if (user){
const salt = user.salt;
const hashedPass = user.password;
const iterations = user.iterations;
// check if input password matches with password in DB
crypto.pbkdf2(password, salt, iterations, 64, 'sha512',
(err, key) => {
if (!err) {
if (hashedPass === key.toString('hex')) {
// password matched
return exits.success({code: 200});
}
}
});
}
// account not found or password doesnt match
return exits.success({code: 404});
}
UserController.js:
login: async function(req, res){
let loginUser;
try {
loginUser = await sails.helpers.login.with({
email: req.body.email, password: req.body.password
});
} catch (e) {
console.log("Error login in usercontroller ", e.message);
return res.serverError();
}
if (loginUser.code == 200) {
return res.ok();
}else {
return res.serverError();
}
}
The problem lies in the Helper when I have the right email and password it's meant to return a code: 200 although it returns code: 404. With error message from node:
WARNING: Something seems to be wrong with this function.
It is trying to signal that it has finished AGAIN, after
already resolving/rejecting once.
(silently ignoring this...)
, same when I input wrong username/ email it returns that message. But when I remove return exits.success({code: 404}) and input right email and password, it returns the right code (200). I need help fixing it.
The problem may be that you are trying to return an error code as a 'success' exit. In your exits object, you should make one like notFound: { responseType: 'notFound' }, then when you want to use it in your code, you do return exits.notFound(). Here is an example: https://sailsjs.com/documentation/concepts/actions-and-controllers#?actions-2
rooms.js -> controller class for rooms endpoint
router.get('/:roomid/fight/verify', function(req, res) {
roomModel.authenticateUserForFight(req.params.roomid, req.query.otp, res);
});
roomModel -> model class for rooms
//authenticate user based on otp provided on client side
exports.authenticateUserForFight = function(roomid, otp, res) {
db.query('select * from room where roomid=?', [roomid], function(error, rows) {
if (rows.length == 0) {
console.log("otp does not exist in db for room:" + roomid);
} else if (rows.length == 1) {
var otpInDb = rows[0].otp.toString();
if (otp == otpInDb) {
console.log("User is authorised");
res.status(200);
res.send("User is authorised");
} else {
console.log("User is unauthorised");
res.status(401);
res.send("User not authorised");
}
}
});
}
This piece of code works fine but is there a better way to send response to client instead of passing res object to model class and setting the status and response message there ? The reason i am passing the res object is because doing res.status and res.send in controller is giving issues as the db call is asynchronous. Suggest some better practices to handle these kind of situtations.
You are right. You should not pass the res object. Its a debugging nightmare if there is more than one place from where the function can exit. Its far better that the subsequent functions return the value and the controller responds to the status.
You can simply create a callback method which will be called once the async db query is completed. Something like this
router.get('/:roomid/fight/verify', function(req, res) {
const callback = (status, message) => {
res.status = status
res.send(message);
}
roomModel.authenticateUserForFight(req.params.roomid, req.query.otp, callback);
});
and the main function can just call this function
//authenticate user based on otp provided on client side
exports.authenticateUserForFight = function(roomid, otp, callback) {
db.query('select * from room where roomid=?', [roomid], function(error, rows) {
if (rows.length == 0) {
console.log("otp does not exist in db for room:" + roomid);
} else if (rows.length == 1) {
var otpInDb = rows[0].otp.toString();
if (otp == otpInDb) {
console.log("User is authorised");
callback(200, 'user authorized');
} else {
console.log("User is unauthorised");
callback(401, 'user not authorized');
}
}
});
}
this is the updated code
if (otp == otpInDb) {
console.log("User is authorised");
res.json({
status:200,
message:"user authorized"
})
} else {
res.json({
status:401,
message:"user not authorized"
})
}
It is always better to send your response in envelope. and I can see you are using String like queries. Use orm wrapper like sequelize to prevent SQL injection attacks
I am trying to use promises to call getLoginState and then store that value so I can use it later.
I am wondering why in the following codeblock, the .then inside of q.fcall(getLoginState) is called before the getLoginState method?
var mysql = require('mysql');
var q = require('q');
var login = "tyler";
var connection = mysql.createConnection({
host : 'localhost',
user: 'root',
password: 'root',
port: '3306',
database: 'root'
});
var gotLoginState;
var state;
connection.connect(function(err) {
if(err != null){
console.log("connection err: "+err);
}
q.nfcall(connection.query.bind(connection),"SELECT id, password, salt, gender, banned, gm, pin, pic, characterslots, tos FROM accounts WHERE name = ?",[login])
.then(function (results) {
console.log("select: "+results[0][0]);
// }).then(q.fcall(getLoginState), function () {
q.fcall(getLoginState)
.then(function() {
console.log("gotLoginState: " + state);
}).catch(function (error){
console.log("error in inner thing");
}).done();
}).catch(function (error) {
console.error("promise error: "+error);
})
.done();
});
var accId = 1;
var getLoginState = function() {
q.nfcall(connection.query.bind(connection), "SELECT loggedin, lastlogin, UNIX_TIMESTAMP(birthday) as birthday FROM accounts WHERE id = ?", [accId])
.then(function (results) {
state = results[0][0].loggedin;
}).catch(function (error) {
console.log("error in chaining: "+error);
}).done();
};
The flow control in promises works like in synchronous code:
To return you use the return keyword.
To signal an error you use t he throw keyword.
The way promises work - is by waiting for them. nfcall is called on a NodeJS style "errback" callback. In order for your code to work you need to return from getLoginState, and then not use nfcall since the method already returns a promise:
var getLoginState = function() { // this should be nodeify probably
return q.nfcall(...) // note the `return` statement here
.then(function (results) {
state = results[0][0].loggedin; // do not mutate state like this
}).catch(function (error) { // instead use return values
console.log("error in chaining: "+error); // don't suppress errors
// logging is not enough
}); // don't do `.done` if you want to chain to it
};
And then in the above part:
// don't use the `.connect` callback, promisify it
q.nfcall(...) // promisify that method _once_ in your real code
.then(function (results) {
console.log("select: "+results[0][0]); // why just the log?
return getLoginState() // note this is a regular function call
.then(function() { // also note the `return`
console.log("gotLoginState: " + state);
}).catch(function (error){
console.log("error in inner thing");
}); // no done here, since we chain to it
}).catch(function (error) {
console.error("promise error: "+error);
}).done(); // you only need one `.done` at the end, and only in old
// promise libraries
I'd like to emphasize that this could could be written better, there is no good reason to nest here rather than chain, and the connecting should be done in a promise - the above code is just the closest thing to your code that works.