I am new to async/await and I'm trying to set a 'user'-constant as the return value of a MySQL query in Node.js. However, the constant does not wait for the return value of the function. How do I use async and await to set 'user' to the return value of the SQL query?
// Should return a user object
const getUserByUsername = async (username, db) => {
const QUERY = `SELECT * FROM ${table_name} WHERE username = '${username}'`;
const user = await db.query(QUERY,
async (err, result) => {
if (!err) {
console.log("name in SQL function: " + result[0].username);
return await result[0];
} else {
console.log(err);
}
}
);
return user;
};
// Does stuff with the user object
const authenticateUser = async (username, password, done) => {
const user = await getUserByUsername(username, db);
console.log("name in main function: " + user.username);
// Trying to do stuff with the user object...
}
What I get in the terminal:
name in main function: undefined
UnhandledPromiseRejectionWarning: Error: data and hash arguments required
at Object.compare
at /app/node_modules/bcrypt/promises.js:29:12
at new Promise (<anonymous>)
at Object.module.exports.promise
etc.....
name in SQL function: john
When you use db.query with a callback, it does not return a promise
Try the following code instead
const getUserByUsername = async (username, db) => {
const QUERY = `SELECT * FROM ${table_name} WHERE username = '${username}'`;
const result = await db.query(QUERY);
console.log("name in SQL function: " + result[0].username);
return result[0];
};
Please try the below code.
const getUserByUsername = async (username, db) => {
try {
const QUERY = `SELECT * FROM ${table_name} WHERE username = '${username}'`;
const user = await db.query(QUERY, async (err, result) => {
if (err) {
throw err;
}
if (result && result.length) {
return result[0];
}
throw new Error(`User with username ${username} not found`);
});
console.log(`name in SQL function: ${user.username}`);
return user;
} catch (error) {
console.log(error);
throw error;
}
};
After jfriend00's comment, I decided to make my function a callback function. I found this answer explaining how to do it: https://stackoverflow.com/a/31875808/6817961. This worked!
My code now:
// Function returns a user object
const getUserByUsername = (username, callback) => {
const QUERY = `SELECT * FROM ${db_name} WHERE username = '${username}'`;
db.query(QUERY, (err, result) => {
if (!err) {
if (result.length > 0) {
return callback(null, result[0]);
} else {
err = "No user with that username";
return callback(err, null);
}
} else {
return callback(err, null);
}
});
};
// Then this function does stuff with the callback
const authenticateUser = (username, password, done) => {
getUserByUsername(username, async (err, result) => {
if (err) {
console.log(err);
return done(null, false, { message: err });
} else {
const user = result;
// result will now return the user object!
Related
i have a class for hash data in nodejs .
this is my class :
const bycrypt = require("bcrypt");
module.exports = new (class Utilitie {
HashField(field) {
return new Promise((resolve, reject) => {
bycrypt.hash(field, bycrypt.genSaltSync(15), (error, hash) => {
if (error) return reject(error);
resolve(hash);
});
});
}
})();
and this is my controller for use this class for hash data :
async ResetPassword(req, res, next) {
const result = await this.ValidationAction(req, res);
if (result[0]) {
let user = await User.findOne({ userName: req.body.userName }).then(
(user) => {
if (!user) return this.Notfound(res);
user.password = Utilite.HashField(req.body.password).then(
(error, hash) => {
console.log("in controller", hash);
}
);
user.save();
}
);
this.Ok(res);
}
return this.BadRerquest(res, result[1]);
}
but it not hash field and it return the undefined .
whats the problem ? how can i solve this problem ????
The argument that your then callback receives will be the hash value. The return value of the then() call will be another promise. You meant to use await:
async ResetPassword(req, res, next) {
const result = await this.ValidationAction(req, res);
if (result[0]) {
const user = await User.findOne({ userName: req.body.userName });
if (!user) return this.Notfound(res);
user.password = await Utilite.HashField(req.body.password);
// ^^^^^
await user.save();
this.Ok(res);
} else {
this.BadRerquest(res, result[1]);
}
}
My backend is consist of Api and DB. When I want to get response from DB I have had delayed output by 1 query.
API (I think api is ok. Start read DB first)
app.post('/api/query', (req, res) => {
console.log(`\n Query input : ${JSON.stringify(req.body)}`);
let queryInput = (Object.values(req.body).join(' '));
if(!dbApi.checkArray(queryInput)){ //If array is not made from clear strings
res.json(dbApi.queryFromUser(queryInput));
}
else{
res.json(dbApi.queryOutput);
}
});
app.listen(dbConfig.server.port, () =>
console.log(`Server running on port ${dbConfig.server.port}`));
DB
queryOutput = [];
const receivingQuery =(queryInput) => {
db.query(queryInput, (err, result) =>{
if(err) throw err+' : '+queryInput;
queryOutput = result;
console.log("\nQuery output "+ JSON.stringify(queryOutput)); //Output (result) is ok
});
return queryOutput //Here is Output from previous query (sends to API)
}
module.exports = {
queryOutput: queryOutput,
queryFromUser: receivingQuery,
}
I tryied callback method and I rewrite it couple of times. But I dont have enough skill to solve it.
If You want to return result of query so simply do following things:
add query method to db module:
function query(sql, args = []) {
return new Promise(function(resolve, reject) {
db.query(sql, args, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
}
// extra feature, getting user by id
async function getUserById(id) {
const result = await query('SELECT * FROM users WHER id = ? LIMIT 1', [id]);
if (Array.isArray(result) && result[0]) return result[0];
return null;
}
module.exports = {
query,
getUserById, // export user by id
queryOutput,
queryFromUser: receivingQuery,
}
use it (with async and await):
app.post('/api/query', async (req, res) => {
try {
console.log('Query input:', req.body);
const queryInput = Object.values(req.body).join(' ');
const result = await dbApi.query(queryInput);
res.json(result);
}
catch (error) {
console.error(error);
res.status(500).json({message: 'Please try again soon'});
}
});
app.get('/api/users/:id', async (req, res) => {
try {
const user = await dbApi.getUserById(req.params.id);
if (!user) return res.status(404).json({message: 'User not found'});
res.status(200).json(user);
}
catch (error) {
console.error(error);
res.status(500).json({message: 'Please try again soon'});
}
});
app.listen(dbConfig.server.port, () =>
console.log('Server running on port', dbConfig.server.port));
I am using mysql package from https://www.npmjs.com/package/mysql
So basically I have this function in a file:
module.exports.listGames = (id) => {
let query = 'SELECT * FROM test';
if (id) {
query += 'WHERE userid = ' + connection.escape(id);
}
connection.query(query, function(err, results) {
if (err) {
throw err;
}
console.log(results);
});
}
and I want to return those results in the json format
so I can call it in another file which is this function:
function sendGames(req, res) {
const games = db.listGames(req.query.game);
res.json(games);
}
so my question is, How can I return the results from that query?
You can either
Use callback
module.exports.listGames = (id, cb) => {
let query = 'SELECT * FROM test';
if (id) {
query += 'WHERE userid = ' + connection.escape(id);
}
connection.query(query, function(err, results) {
if (err) {
throw err;
}
cb(results);
});
}
module.exports.listGames(12, (results) => {
console.log(results);
})
Use promise
module.exports.listGames = (id) => {
let query = 'SELECT * FROM test';
if (id) {
query += 'WHERE userid = ' + connection.escape(id);
}
return new Promise((resolve, reject) => {
connection.query(query, function(err, results) {
if (err) {
throw err;
}
resolve(results);
});
})
}
module.exports.listGames(12).then(results => console.log(results)
You can encode the response from mysql query to JSON using JSON.stringify.
module.exports.listGames = (id) => {
let query = 'SELECT * FROM test';
if (id) {
query += 'WHERE userid = ' + connection.escape(id);
}
connection.query(query, function(err, results) {
if (err) {
throw err;
}
return (results);
});
}
Declare javascript async function and call the method from here.
async function sendGames (req, res) {
var games = await db.listGames(req.query.game);
res.json(games);
}
I'm calling a function to see if an email adress is in the db. It's either an empty or filled array. Whenever i return this value it is undefined afterwards. How can I solve this? Thanks in advance!
I have tried using next() and promises.
The function that calls the db query function.
const emailExists = async function(req,res,next){
let emailInDb = await usermodel.getOneByEmail(req, next);
console.log(emailInDb);
if(emailInDb.length !==0){
res.send('email already in use!');
}
else{
return next();
}
};
The db query.
const getOneByEmail = function (req, next){
let db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error(err.message);
}
console.log('Connected to books db.');
});
db.all(`SELECT * FROM users WHERE email = ?`, [req.body.email],
(err, rows) => {
console.log(rows);
return rows;
});
};
console.log(emailInDb) gives undefined.
While console.log(rows) gives an array with results.
Putting await in front of a non async function does not magically make the function wait for the db-calls to resolve. You need to wrap the db-calls in a promise, e.g:
const getOneByEmail = (req, next) => {
return new Promise((resolve, reject) => {
let db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error(err.message);
reject(err);
}
console.log('Connected to books db.');
});
db.all(`SELECT * FROM users WHERE email = ?`, [req.body.email],
(err, rows) => {
// TODO: check for error here and reject
console.log(rows);
resolve(rows);
});
});
};
Note that you shouldn't connect to the database each time you call getOneByEmail, instead reuse the connection...
I'm trying to get async / await to trigger events in order, but it seems I'm missing something as my console.log markers are triggering in reverse to the order I was hoping for.
I 'm wondering if is to do with my use of nested functions in users.js but having tried multiple variations of async / await, it consistently doesn't work as expected.
// index.js
var users = require("./users.js");
app.post("/getToken", async function(req, res) {
if (req.body.email && req.body.password) {
const email = req.body.email;
const password = req.body.password;
const user = await users(email, password)
// running this should output console.log("No 1")
// from users.js first, but doesn't ?
console.log('No 2')
if (user) {
var payload = {
id: user.id
};
var token = jwt.encode(payload, cfg.jwtSecret);
res.json({
token: token
});
} else {
res.sendStatus(401);
}
} else {
res.sendStatus(401);
}
});
// users.js
module.exports = function(emailAddress, password) {
db.connect();
var query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
var query_params = [emailAddress];
db.query(
query,
query_params,
function(error, result, fields) {
console.log('No 1')
if (error) throw error;
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
return { id: result[0].id }
} else {
console.log("login False | Password");
return false;
}
} else {
console.log("login False | username");
return false;
}
}
)
}
Your users.js function doesn't return anything. The callbacks you're passing query do, but the overall function doesn't. Since it never returns anything explicitly, the result of calling it is undefined. If you await undefined, it's like await Promise.resolve(undefined) and so your resolution handler is called quite quickly.
You want that function to return a promise that doesn't get resolved until the work is done. Since what it uses is an old-style Node callbck API, it's reasonable to use new Promise to create that promise (alternately, get or create a promise-enabled API to that DB).
I also suspect you're calling connect incorrectly, since normally that would be an asynchronous action, but you're treating it as though it were synchronous.
See comments:
users.js
module.exports = function(emailAddress, password) {
return new Promise((resolve, reject) => {
// Use the callback to know when the connection is established
db.connect(error => {
if (error) {
// Connection failed
reject(error);
return;
}
var query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
var query_params = [emailAddress];
db.query(
query,
query_params,
function(error, result, fields) {
// Throwing an error here does nothing useful. Instead,
// reject the promise.
if (error) {
reject(error);
return;
}
// Resolve our promise based on what we got
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
resolve({ id: result[0].id });
} else {
console.log("login False | Password");
resolve(false);
}
} else {
console.log("login False | username");
resolve(false);
}
}
);
});
});
}
Then using it:
app.post("/getToken", async function(req, res) {
// You must handle errors, since `post` won't do anything with the return
// value of this function
try {
if (req.body.email && req.body.password) {
const email = req.body.email;
const password = req.body.password;
// Now this waits here, since `users` returns a promise that
// isn't resolved until the query completes
const user = await users(email, password)
console.log('No 2')
if (user) {
var payload = {
id: user.id
};
var token = jwt.encode(payload, cfg.jwtSecret);
res.json({
token: token
});
} else {
res.sendStatus(401);
}
} else {
res.sendStatus(401);
}
} catch (e) {
res.sendStatus(401);
}
});
The problem is that db.query function is asynchronous - you are providing callback function that is executed when database call is finished. You probably need to wrap this whole function in Promise:
module.exports = function(emailAddress, password) {
return new Promise(function(resolve, reject) {
db.connect();
var query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
var query_params = [emailAddress];
db.query(
query,
query_params,
function(error, result, fields) {
if (error) return reject(error)
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
resolve({id: result[0].id})
} else {
console.log("login False | Password");
reject();
}
} else {
console.log("login False | username");
reject();
}
}
)
})
}
You can learn more about Promise API here
EDIT:
So you should additionally make connect synchronous. Here's a piece of code I have refactored for you. It should work just fine. I have used some ES6 elements to make it more readable.
const connect = () => new Promise((resolve, reject) => {
db.connect((err) => {
if (err) return reject(err);
resolve();
})
})
const makeDbRequest = (emailAddress, password) => new Promise((resolve, reject) => {
const query = `
SELECT
id,
email,
password,
salt
FROM
users
WHERE
email = ?`;
const query_params = [emailAddress];
db.query(
query,
query_params,
handleDbData(resolve, reject, password),
);
})
const handleDbData = (resolve, reject, password) => (error, result, fields) => {
if (error) return reject(error)
if ( result.length == 1 ) {
if ( checkPass(password, result[0].password, result[0].salt ) ) {
resolve({id: result[0].id})
} else {
console.log("login False | Password");
reject();
}
} else {
console.log("login False | username");
reject();
}
}
module.exports = (emailAddress, password) => new Promise((resolve, reject) => {
connect()
.then(() => {
makeDbRequest(emailAddress, password)
.then(resolve)
.catch(reject)
})
.catch(reject);
})