Async function executing in wrong order - node.js

I'm trying to write a function in nodejs that reads the information about users from a json file (I know it's not secure, just an exercise), logs whether the details are correct and sends a response.
I wrote this async function:
async function getUser(sUsername) {
fs.readFile('db/users.json', (err, data) => {
if (err) {
console.log(`An error has occured in getUser function`)
}
let users = JSON.parse(data)
userNamesFromFile = Object.keys(users);
console.log(`Data is: ${data}`)
console.log(`Users after parsing are: ${userNamesFromFile}\n`)
for (currUser in userNamesFromFile) {
`Currently checking comparing to user: ${currUser}\n`
if (currUser === sUsername) {
var oUser = new User(currUser, users[currUser].password);
return oUser;
}
}
})
}
And I call it here :
getUser(oBody.username.toLowerCase()).then(oFoundUser => {
console.log(`Finished the async function, found user ${oFoundUser}`)
if (oFoundUser) {
if (oBody.password.toString() === oFoundUser.password.toString()) {
console.log("Login successful")
res.statusCode = 200
req.session.username = oFoundUser.username;
}
else {
console.log("Wrong password")
res.statusCode = 401;
}
}
else {
res.statusCode = 401
console.log("No user with such a name exists")
}
res.send();
})
This is the log I get from the server when inputting the correct details of a user:
Finished the async function, found user undefined
No user with such a name exists
POST /login 401 7.822 ms - -
Data is: {
"testuser1": {
"password": 12345
},
"testuser2": {
"password": 6789
}
}
Users after parsing are: testuser1, testuser2
It looks like he's saying that the async function runs and only then logs the information I'm logging from the async function. It doesn't even get into the loops comparing the users to what I just got from my json file.
I put what I want to do with the return value of the async function in a .then. Shouldn't it run in the order I expect it to?
Thanks,
Ben

I found a solution. I guess using a callback is the right answer. If I understand correctly, the async function doesn't return the promise that resolves as the "return" value I gave it, but I can call a comeback inside the resolving in the async function. Is this correct?
async function getUser(sUsername, callback) {
return fs.readFile('db/users.json', (err, data) => {
if (err) {
console.log(`An error has occured in getUser function`)
}
let users = JSON.parse(data)
userNamesFromFile = Array.from(Object.keys(users));
for (let i = 0; i < userNamesFromFile.length; i++) {
currUser = userNamesFromFile[i]
if (currUser === sUsername) {
var oUser = new User(currUser, users[currUser].password);
console.log(`Returnign callback with ${oUser.toString()}` )
return callback(oUser);
}
if (i === userNamesFromFile.length - 1) {
return callback(false)
}
}
})
}
getUser(oBody.username.toLowerCase(), oFoundUser => {
console.log(`Finished the async function, found user ${oFoundUser}`)
if (oFoundUser) {
if (oBody.password.toString() === oFoundUser.password.toString()) {
console.log("Login successful")
res.statusCode = 200
req.session.username = oFoundUser.username;
}
else {
console.log("Wrong password")
res.statusCode = 401;
}
}
else {
res.statusCode = 401
console.log("No user with such a name exists")
}
res.send();
})
Thanks,
Ben

Related

Why I can not get correct response from api?

I can't get a good answer from my api , for example I try to read "true" or "false" from api to give the authorization for user , but I got just undefined data.
the methods both work prefectly from sending data to verify the user .
I have this api method inside server :
router.get("/sickers/user/login/:mail/:pass", (req, res) => {
//var values = JSON.parse(req.body);
var pass = req.params.pass;
var email = req.params.mail;
//console.log(values);
if (pass !== null || pass !== "") {
try {
con.connect();
con.query("SELECT Password FROM `sickers` WHERE Email='" + email + "'", function(err, rows, field) {
if (err) {
console.log(err);
res.send("an error detected try later");
} else {
try {
if (pass == rows[0].Password) {
res.json({ "result": "true" })
} else {
res.json({ "result": "false" })
}
} catch {
res.json({ "result": "false" })
}
}
});
} catch (e) {
res.send("no data found");
console.log("obj not found");
}
}
con.end();
});
and this call api inside my react app :
submithandler(e) {
e.preventDefault();
const url = 'http://localhost:8000/api/sickers/user/login/'+this.state.email+'/'+this.state.password+'';
const res = fetch(url);
const data = res;
this.setState({result:data});
alert(this.state.result);
}
thanks.
Account for the async nature of the functions you are using. It might look something like this:
const url = 'http://localhost:8000/api/sickers/user/login/'+this.state.email+'/'+this.state.password+'';
// Use .then to call a function AFTER the fetch has completed
fetch(url).then(result => result.json()).then((response) => {
// Use the setState callback to check updated values AFTER they have been updated
this.setState({result: response}, () => {
alert(this.state.result);
});
});
Docs on fetch
Docs on setState

Conditionally send responses in an Express app

I'm curious whether you can write if statements in an Express app to conditionally execute your code without providing else statements.
if(pred) {
doSomething()
}
return foo;
calcBar(); // doesn't run.
Above is the synchronous code that stops execution after the return statement.
My Express function looks like this:
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
if(data.length === 0) {
res.json({ message: "No data." });
}
const someOtherData = await someOtherGraphQLCall(data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({err})
}
}
I know because of this question that code after the first res.json might still be executed. Is there a way to stop that? I don't want the second GraphQL call to execute if the first if condition is met. Is that possible without using else ?
Edit:
As the question I linked above mentioned, using a return statement is a bad option because:
it also makes it less meaningful and vague, cause it uses incorrect semantics. If you are not using the value from the function, then you shouldn't return one.
You can use return keyword on the first response to immediately return from the function.
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
if(data.length === 0) {
return res.json({ message: "No data." });
}
const someOtherData = await someOtherGraphQLCall(data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({err})
}
}
Edit:
As an alternative, you can split the logic of the data and building up response. This way you can use return and it's easier to read:
app.get('/matches', async function (req, res) {
try {
const data = await getDataFromGraphQLCall();
res.json(data);
} catch (err) {
res.json({ err })
}
});
async function getDataFromGraphQLCall() {
const data = await someGraphQLCall();
if (data.length === 0) {
return { message: "No data." };
}
const someOtherData = await someOtherGraphQLCall(data.foo);
return { someOtherData };
}
If you are wondering if there is a way to achieve that without the else, yes it is.
But, It might not be THE cleanest way. IMO, using return is the best way to stop the execution of the controller.
Anyways, You can split the chunk of code into middlewares and use ternary operator to conditionally send responses.
In your example, separate out data = await someGraphQLCall(); as follows:
const middlewareOne = async function(req, res, next) {
let data = [];
let response = { message: "No data." };
try {
data = await someGraphQLCall();
req.locals.data = data; // <- attach the data to req.locals
} catch (err) {
response = { err };
}
data.length === 0 ? res.json(response) : next();
};
And then, mount the middlewareOne BEFORE your controller:
app.get("/matches", middlewareOne, async function controller(req, res) {
try {
const someOtherData = await someOtherGraphQLCall(req.locals.data.foo);
res.json({ someOtherData });
} catch (err) {
res.json({ err });
}
});
How this works is, the controller function would only be executed by express if the next() is called from the previous middleware -- middlewareOne in the example.
And as middlewareOne only calls next() if the data.length is not 0, it would work as you expected.
For more information on passing data from one middleware to other, read this
The return statement terminates the function execution in this context. In my opinion, you should handle the success case then the error case since the code will be read top to bottom.
In if statement, data could be undefined or null.
You can read more here: MDN - return
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
// alternative, if (data && data[0]) {
if (data && data.length) {
const someOtherData = await someOtherGraphQLCall(data.foo);
return res.json({ someOtherData });
}
return res.json({ message: "No data." });
} catch (err) {
console.log(err); // log error with logger and drain to loggly.
res.json({ err })
}
}
With Void operator:
Void operator allows you to return undefined but evaluate the given expression.
You can read more here: MDN - Void
app.get('/matches', async function(req, res) {
try {
const data = await someGraphQLCall();
// alternative, if (data && data[0]) {
if (data && data.length) {
const someOtherData = await someOtherGraphQLCall(data.foo);
return void res.json({ someOtherData });
}
return void res.json({ message: "No data." });
} catch (err) {
console.log(err); // log error with logger and drain to loggly.
res.json({ err })
}
}

Nodejs WebService database Select

I am trying to do Nodejs database operations through the class, the route and method I pass through the route reaches the model and the select operation is done.
but when I send the data it is "undefined" in app.use
Route Code
var Select = require('../models/queryClass');
obj = new Select();
router.use(function (req, res) {
res.json(obj.Data(req.method, req.path))
});
Model Code
class Select {
Data(metodh, path) {
if (metodh == "GET" && path == "/data") {
db.query("select * from data ", function (err, result) {
if (err) {
return err;
}
else {
return result
}
})
}
}
};
The problem is that inside anonymous function the response is comming asynchronously and you never actually return the value outside that function.
Try to use promises, which should look something like this:
class Select {
Data(metodh, path) {
if (metodh == "GET" && path == "/data") {
return new Promise((resolve, reject) => {
db.query("select * from data ", function (err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
})
});
}
}
};
And to get the answer you have to use the promise way; (then - catch)
obj.Data(req.method, req.path)
.then(result => console.log(result))
.catch(err => console.log(err))
Note: I didn't test it, but this way it should work. Before you never actually returned the result since it was async.

Server sends success even if didn't find anything

I send data from my input fields to my api:
$.ajax({
url: '/api/login',
type: 'GET',
dataType: 'json',
ContentType: 'application/json',
data: {formData},
success: (data) => {
console.log('SUCCESS')
console.log(data)
this.setState({
isInProcess: false
})
},
error: (jqXHR) => {
console.log(jqXHR)
console.log('ERROR')
this.setState({isInProcess: false})
}
})
on my server-side I have a function to see if I have required user in db:
async function findUser(data) {
try {
const user = await User.findOne({email: data.email,
password: data.password})
console.log('User was found')
return { user }
} catch (err) {
console.log('error', err)
throw err
}
}
which will be executed here:
app.get('/api/login', async (req, res) => {
const data = req.query
try {
const foundUserData = await findUser(data.formData)
return res.json(foundUserData)
} catch (err) {
return res.status(400).json(err)
}
})
It works fine, but if a user wasn't found in db i sends success anyway. Why?
await findUser(data.formData) won't throw error, return either null or user object. You may check something following
app.get('/api/login', async (req, res) => {
const data = req.query
try {
const foundUserData = await findUser(data.formData)
if(foundUserData && foundUserData.user) {
return res.json(foundUserData)
} else {
return res.status(400).json({message: 'User not found'});
}
} catch (err) {
return res.status(500).json(err)
}
})
It sends success because none of your queries error'ed, just because it didn't find anything does not mean that the query failed because it obviously succeeded in finding out if what ever you're looking for exists or not.
To send an error in case of not found you need to check if response is empty in which case you want to send error
When no user is find you get a null value. You may try to put more logic on your success parameter with that for example:
success: function (data) {
if(!!data && data != null) {
alert('Success');
} else {
alert('No data');
}
}

What can be a better way to handle sending response from controller in nodejs project?

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

Resources