Trying to understand Async/Await - javascript

I have an async function 'query' that 'awaits' for the pool.query to return the results.
// db.js
const pool = new pg.Pool({
connectionString: isProduction ? process.env.DATABASE_URL : connectionString,
ssl: isProduction,
});
export const query = async ({ text, values }) => {
const start = Date.now();
try {
const results = await pool.query(text, values);
const duration = Date.now() - start;
logger.info(`executed query: ${text} duration: ${duration} rows: ${results.rowCount}`);
return results.rows;
} catch (e) {
logger.error(`error: ${e}`);
}
};
In another async function getUser() I'm 'awaiting' the query function to finish before returning the data.
// users.js
export const getUser = async (email) => {
const text = `
SELECT (user_id, email) FROM users
WHERE email = $1
`;
const values = [email];
try {
const data = await query({ text, values });
// ^ vscode says above await is doing nothing
return data.rows[0];
} catch (e) {
logger.error(`error: ${e}`);
}
};
Then in yet another async function I'm awaiting the getUser function
// auth.js
export const SignIn = async (email, password) => {
const userRecord = await getUser(email);
if (!userRecord) {
throw new Error('User not registered');
}
logger.silly('Checking password');
const validPassword = await argon2.verify(userRecord.password, password);
if (validPassword) {
logger.silly('Password is valid!');
logger.silly('Generating JWT');
const token = await generateToken(userRecord);
const user = { id: userRecord.id, email: userRecord.email };
return { user, token };
} else {
throw new Error('Invalid Password');
}
};
Inside vsCode I'm getting a warning "await has no effect on the type of this expression." only when 'awaiting' the query function call, but not when 'awaiting' the getUser function call. What am I missing here?

What does query return? If results.rows is a promise then that might explain what you're seeing.

Related

nodejs javascript promise resolve

I can't seem to figure out how to save the results of SomeQuery promise. Essentially I would like to take the value in res and pipe it into parseQuery function and return the final results. How do I make the parsed result accessible to an APIs response.
const neo4j = require('neo4j-driver')
var parser = require('parse-neo4j')
const astria_queries = require('./astriaQueries')
const uri = 'bolt://astria_graph:7687'
const user = 'xxx'
const password = 'xxx'
const someQuery = (query) => {
// run statement in a transaction
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password))
const session = driver.session({ defaultAccessMode: neo4j.session.READ })
const tx = session.beginTransaction()
tx.run(query)
.then((res) => {
// Everything is OK, the transaction will be committed
parseQuery(res)
})
.then(() => {
// Everything is OK, the transaction will be committed
})
.catch((e) => {
// The transaction will be rolled back, now handle the error.
console.log(e)
})
.finally(() => {
session.close()
driver.close()
})
}
const parseQuery = (result) => {
try {
const test = parser.parse(result)
console.log(test)
} catch (err) {
console.log(err)
}
}
module.exports = {
someQuery,
}
It finally clicked with me. Here is the solution I came up with. Hopefully it will help others. If there is a better way please let me know. Thank you #fbiville for you help.
async actions
const neo4j = require('neo4j-driver')
var parser = require('parse-neo4j')
const astria_queries = require('./astriaQueries')
const uri = 'bolt://astria_graph:7687'
const user = 'neo4j'
const password = 'neo'
async function getRecords(query) {
// run statement in a transaction
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password))
const session = driver.session({ defaultAccessMode: neo4j.session.READ })
const tx = session.beginTransaction()
try {
const records = await tx.run(query)
const parseRecords = await parseQuery(records)
return parseRecords
} catch (error) {
console.log(error)
} finally {
session.close()
driver.close()
}
}
async function parseQuery(result) {
try {
const parsedRes = await parser.parse(result)
// console.log(parsedRes)
return parsedRes
} catch (err) {
console.log(err)
}
}
// getRecords(astria_queries.get_data_sources)
module.exports = {
getRecords,
}
api send()
exports.get_data_sources = async (req, res) => {
try {
queryFuns.getRecords(astria_queries.get_data_sources).then((response) => {
res.send(response)
})
} catch (error) {
res.status(500).send(error)
console.log(error)
}
}

Where is my error in using promises in node.js

I am still very new to node.js. In my current test project I want to send a confirmation email or other emails, depending on the loaded template. The template is stored in MySQL.
The result I am getting is:
{
"message": {
"error": {},
"foo": "bar"
}
}
So the error bit is empty and I don't know why...
If I reject manually at a different point in the code it works just fine, so the problem is not with the middleware, router or server.js file.
Also I have rejected "Foo: Bar" back, to check which catch block catched the error.
Here is my mailer.js file:
const nodemailer = require('nodemailer');
let conDB;
module.exports = (injectedMySql) => {
conDB = injectedMySql
return {
sendMail: sendMail
}
}
const sendMail = (mail) => {
return new Promise((resolve,reject) => {
loadTemplate(mail.templateId, mail.languageId)
.then(data => {
const mailserver = {
host: "something.com",
port: 465,
secure: true, // use TLS
auth: {
user: "something#something.com",
pass: "PASSWORD"
},
tls: {
// do not fail on invalid certs
rejectUnauthorized: false
}
};
const body = {
from: 'something#something.com',
to: mail.toAdress,
subject: allReplace(data.subject, mail.subjectReplace),
text: allReplace(data.body, mail.textReplace),
html: allReplace(data.html, mail.htmlReplace)
}
// create a nodemailer transporter using smtp
let transporter = nodemailer.createTransport(mailserver)
transporter.sendMail(body)
.then(data => {console.log(data)
resolve(data)
})
.catch(err => {reject("sendMail problem")})
})
.catch(error => {reject({"error": error, "foo": "bar"})})
})
}
function allReplace (str, obj) {
var retStr = str;
for (var x in obj) {
retStr = retStr.replace(new RegExp(x, 'g'), obj[x]);
}
return retStr;
};
const loadTemplate = (mailTemplate, languageId) => {
return new Promise((resolve,reject) => {
if(mailTemplate === null || languageId === null)
reject("nop, something is missing");
else
{
if (typeof conDB.query === "function")
{
conDB.query('SELECT * FROM email_template WHERE language_id = ? AND template_id = ?', [mailTemplate,languageId])
.then(data => {resolve(data)})
.catch(err => {reject("mysql has a problem")})
}
else
{
reject("function is not available");
}
}
})
}
Here is my mysql.js file:
var mysql = require('mysql2/promise');
const databaseConfigs = {
host: 'localhost',
user: 'USERNAME',
password: 'PASSWORD',
database: 'DBNAME'
};
const createID = table => {
return new Promise((resolve,reject) => {
//execute the query to register the user
let query = '';
let id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
query = `SELECT * FROM ${table} WHERE id = ?`
this.query(query,[table,id])
.then(data => {
console.log(data[0].length)
if(data[0].length==0)
{
resolve(id)
}
else
{
createID(table)
.then(data => {resolve(data)})
.catch(error => {reject(error)})
}
})
.catch(error => {reject(error)})
})
}
async function query (sql,att) {
let connection = await mysql.createConnection(databaseConfigs);
return new Promise( ( resolve, reject ) => {
console.log(`Query: '${sql}'`);
connection.query(sql,att)
.then(data => {resolve(data)})
.catch(error => {reject(error)})
connection.end();
});
}
async function transaction(queries, queryValues) {
if (queries.length !== queryValues.length) {
return Promise.reject(
'Number of provided queries did not match the number of provided query values arrays'
)
}
const connection = await mysql.createConnection(databaseConfigs)
try {
await connection.beginTransaction()
const queryPromises = []
queries.forEach((query, index) => {
queryPromises.push(connection.query(query, queryValues[index]))
})
const results = await Promise.all(queryPromises)
await connection.commit()
await connection.end()
return results
} catch (err) {
await connection.rollback()
await connection.end()
return Promise.reject(err)
}
}
module.exports.transaction = transaction;
module.exports.query = query;
module.exports.createID = createID;
Thanks to you all!
Chris
I cleand up your code a bit. Specially the error handling as you always mask your errors with your Promise.reject("message").
I think what confused you is that you're already using libraries which work with promise (you don't need to wrap those into promises again). Thats quite good as you just can use async/await then.
I hope it helps. If something is unclear just ask.
const nodemailer = require('nodemailer');
let conDB;
module.exports = (injectedMySql) => {
conDB = injectedMySql
return {
sendMail: sendMail
}
}
// your load template function already uses promises no need to wrap it
const sendMail = async mail => {
const data = await loadTemplate(mail.templateId, mail.languageId)
const mailserver = {
host: "something.com",
port: 465,
secure: true, // use TLS
auth: {
user: "something#something.com",
pass: "PASSWORD"
},
tls: {
// do not fail on invalid certs
rejectUnauthorized: false
}
};
const body = {
from: 'something#something.com',
to: mail.toAdress,
subject: allReplace(data.subject, mail.subjectReplace),
text: allReplace(data.body, mail.textReplace),
html: allReplace(data.html, mail.htmlReplace)
}
// create a nodemailer transporter using smtp
let transporter = nodemailer.createTransport(mailserver)
try {
// Return the value of sendmail
return await transporter.sendMail(body);
} catch (err) {
// handle error or throw it. I'll throw as you rejected the Promise here it.
// this part will actually help you as you now can see the correct error instead of your rejected "foo bar" erro object
throw err;
}
}
function allReplace(str, obj) {
var retStr = str;
for (var x in obj) {
retStr = retStr.replace(new RegExp(x, 'g'), obj[x]);
}
return retStr;
};
const loadTemplate = async (mailTemplate, languageId) => {
if (mailTemplate === null || languageId === null)
throw new Error("nop, something is missing");
else {
if (typeof conDB.query === "function") {
try {
const data = await conDB.query('SELECT * FROM email_template WHERE language_id = ? AND template_id = ?', [mailTemplate, languageId]);
} catch (err) {
// it's better to use the real error you always hide the real reason why something went wrong with your promise reject :).
throw err;
}
}
else {
throw new error("function is not available");
}
}
}
.
var mysql = require('mysql2/promise');
const databaseConfigs = {
host: 'localhost',
user: 'USERNAME',
password: 'PASSWORD',
database: 'DBNAME'
};
const createID = async table => {
// use GUID? https://www.npmjs.com/package/guid
let id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
let query = `SELECT * FROM ${table} WHERE id = ?`
try {
data = await this.query(query, [table, id]);
} catch (error) {
// as we throw the error in query we got to catch it here
// handle it or throw it (I throw it because I can't handle it ;).)
throw error;
}
console.log(data[0].length)
if (data[0].length == 0) {
return id;
} else {
return await createID(table);
}
}
const query = async (sql, att) => {
let connection = await mysql.createConnection(databaseConfigs);
console.log(`Query: '${sql}'`);
try {
const data = await connection.query(sql, att);
return data;
} catch (error) {
// Handle error or throw it again
// you rejected the promise so i throw it here
throw error;
} finally {
connection.end();
}
}
// I changed it to make it the same as the other functions from this
// async function transaction(queries, queryValues) { to
const transaction = async (queries, queryValues) => {
if (queries.length !== queryValues.length) {
// just throw an error
throw new Error('Number of provided queries did not match the number of provided query values arrays');
}
const connection = await mysql.createConnection(databaseConfigs)
try {
await connection.beginTransaction()
const queryPromises = []
queries.forEach((query, index) => {
queryPromises.push(connection.query(query, queryValues[index]))
})
const results = await Promise.all(queryPromises)
await connection.commit()
await connection.end()
return results
} catch (err) {
await connection.rollback()
await connection.end()
// this is not needed
// return Promise.reject(err)
// if you don't want to handle it here just throw the error
throw err;
}
}
module.exports.transaction = transaction;
module.exports.query = query;
module.exports.createID = createID;

Error acessing mongo collection using mongo client asynchronously

I've built the following mongo client access bootstrap file:
import { MongoClient } from "mongodb";
let db = null;
// Connect to mongo
const uri = "mongodb://localhost/mydb";
const opts = { useUnifiedTopology: true };
const connect = async () => {
console.log("Connecting to database...");
let client = await MongoClient.connect(uri, opts).catch(error => {
console.log("Error connecting to database: " + err);
});
if (client) {
console.log("Database connected.");
db = client.db("mydb");
}
return client;
};
// Get database connection
const getDb = async () => {
if (!db) await connect();
return db;
};
// Get Collection
const getCollection = async name => {
let database = await getDb();
let collection = await database.collection(name);
if (!collection)
throw new Error("(mongo) Cannot get collection named " + name);
return collection;
};
export { db, getCollection };
When trying to acess the collection for the first time in another program:
import { getCollection } from "./mongoutils";
const init = async () => {
let user = await getCollection("users").findOne({ name: "Josh"});
console.log("User found!");
}
I'm getting the following error:
UnhandledPromiseRejectionWarning: TypeError: (0 , _mongo.getCollection)(...).findOne is not a function
How can I properly fix this error, keeping the whole structure async/await?
An async function returns a promise and not the resolved data.
Here getCollection() is an async function. So, calling getCollection("users") would return a promise and not the resolved collection itself as I presume what you are expecting. The correct way to do is:
import { getCollection } from "./mongoutils";
const init = async () => {
let userCollection = await getCollection("users");
try {
let user = await userCollection.findOne({ name: "Josh"})
console.log("User found!");
} catch (e) {
console.log("User not found!");
}
}

Async | Can not get my async promise to function

I'm currently writing a login server function and I cannot figure out why the posmise that I'm making isnt being called
Im using mongodb with mongoose as the backend that is being connected to using the User.findOne which works. I cannot figure out why the bottom console.log for Test and test2 never get fired.
I'm quite new to the promise and async and I can't figure out what im missing
class HandlerGenerator {
login (req, res) {
console.log('debguh1')
let username = req.body.username;
let password = req.body.password;
let checkUsername = "";
let checkPassword = "";
var lData = {
username: req.body.username,
password: req.body.password
};
var myPromise = () => {
return new Promise((resolve, reject) => {
console.log('db2')
User.findOne(lData , function(err, userLogin){
if(err){
console.log(err);
return
}
console.log(userLogin)
checkUsername = userLogin.username;
checkPassword = userLogin.password;
});
});
};
var callMyPromise = async () => {
var result = await (myPromise());
// call code here
console.log("Test1")
resolve(result);
//return result
};
callMyPromise().then(function(result) {
console.log('Test2')
res.json(result);
});
}
}
I guess you are using mongoose for your queries, .exec() is returning a promise.
By making your login method async you can now await the response. But do make sure to wrap an await in a try catch block or HOF, to handle errors / or not found users.
class HandlerGenerator {
async login(req, res) {
let { username, password } = req.body;
let checkUsername = "";
let checkPassword = "";
var lData = {
username
password
};
try {
const userData = await User.findOne(lData).exec();
res.json(userData);
} catch (e) {
// do some error handling in here
}
}
}

JavaScript Unable to get value returned from a then() call to another then() call

I am trying to execute this code:
UserSchema.methods.generateAuthToken = function () {
const user = this;
const access = 'auth';
const token = jwt.sign({_id: user._id.toHexString(), access}, 'abc123');
user.tokens.push({access, token});
user.save().then(() => {
return token;
});
};
In my express file, I have following code:
app.post('/users', (req, res) => {
const body = _.pick(req.body, ['email', 'password']);
const user = new User(body);
user.save().then(() => {
return user.generateAuthToken();
}).then((token) => {
res.header('x-auth', token).send(user);
}).catch((err) => {
console.log(err);
res.status(400).send(err);
});
});
The problem is that the token returned from the user.save().then() call in first file never reaches the express code user.generateAuthToken(). What is the reason and how should I solve it?
generateAuthToken has no return statement.
If you want to return the promise that then returns, then you have to do so explicitly.
you need to update your schema to the following:
UserSchema.methods.generateAuthToken = function () {
const user = this;
const access = 'auth';
const token = jwt.sign({_id: user._id.toHexString(), access}, 'abc123');
user.tokens.push({access, token});
user.save().then(() => {
return token;
});
};
The generateAuthToken does not return a Promise in your code

Categories

Resources