Await sql and push to array before executing the result - javascript

I'm using mysql npm package to get data from the database.
My goal is to add every list(id) from lists Table and push it into lists array.
I'm getting the correct data from the database but when I result the query lists array is empty.
I think that I have to add async and await to the code to make it work. Tried in several places but I didn't make it work. Any idea what I'm doing wrong?
// GET - get all grocery_lists
Grocery_list.getAll = (result) => {
let lists = []; // <--- List Array
sql.query("SELECT id FROM lists", (err, res) => { // <--- Get all id from 'lists' table
if (err) {
console.log(err);
}
res.forEach(list => { // <--- Loop thru all lists
sql.query(`
SELECT items.id, items.name, items_x_lists.purchased
FROM items_x_lists
INNER JOIN items ON items_x_lists.itemId = items.id
WHERE items_x_lists.listId = ${list.id};
`, (err, res) => { // <--- Get all items for ${list.id} from 'items' table
if (err) {
console.log(err);
}
const list = {};
list.id = res.id;
console.log(list); // <--- { id: 1 } ... { id: 2 }
lists.push(list);
});
});
result(null, lists); // <--- returning empty array instead of [{ id: 1 }, { id: 2 }]
});
};

I think you can do it simply like this (not the exact code) but I think you've got the idea:
// GET - get all grocery_lists
Grocery_list.getAll = (result) => {
getData().then(data => {
// call result(data);
}).catch(err => {
// handle error for no data or any other errors
})
}
const getData = async () => {
try {
var res = await sql.query("SELECT id FROM lists");
// Handle error if no data found
if (!res) { Promise.reject("No data found"); }
} catch (error) {
return Promise.reject(error);
}
const listIds = res.map(id => id); // array of ids
try {
var data = await sql.query(`
SELECT items.id, items.name, items_x_lists.purchased
FROM items_x_lists
INNER JOIN items ON items_x_lists.itemId = items.id
WHERE items_x_lists.listId in ${listIds};`);
// Handle error if no data found
if (!data) { return Promise.reject("No data found") }
} catch (error) {
return Promise.reject(error);
}
return Promise.resolve(data)
}

this mighht be the solution
Grocery_list.getAll = async (result) => { return sql.query("SELECT id FROM lists", (err, res) => { // <--- Get all id from 'lists' table if (err) { console.log(err); } }); };

I’m outside, so I can only edit with my mobile phone. If the layout is strange, I apologize for it.
First of all, if it is me, I will adjust a few places
1.Create an async function and use Promise.all technology
async function queryAll(querys){
return await Promise.all(querys);
}
2.Create a Promise function to execute each sql
const queryPromise = id => {
return new Promise((resolve, reject) => {
sql.query(
'SELECT items.id, items.name, items_x_lists.purchased FROM items_x_lists INNER JOIN items ON items_x_lists.itemId = items.id WHERE items_x_lists.listId = ? ;',
id,
(err, rows, fields) => {
console.log(rows);
if (err) reject(err);
else resolve(rows);
}
);
});
};
3.Adjust the internal logic of the getAll event
const querys = []
res.forEach(list => {
querys.push(queryPromise(list.id));
});
const querysResults = await queryAll(querys);// remember to use async for function
querysResults.forEach(querysResult => {
lists.push({id:querysResult['id']});
});
Because there is no computer on hand, there may be some situations that need to be judged, but roughly this will work normally.
Hope this helps you :)

Related

JavaScript - Async/Await - How to Return result from nested functions?

I'm new to Node.js and having difficulty working with async/await model. The issue I'm having is with nested function calls returning data in an async/await method, I always get 'undefined'. My functions structure is like this:
const findCustomerOrder = async (id) => {
const orders = await Order.find({custoemrId:id})
.then(async (order) => {
if(order)
{
const products = await findProductsByOrderId(order._id)
.then(async (result) => {
for(const r in result){
console.log("Product ID: ", result._id);
}
});
}
else
console.log("order not found");
});
}
const findProductsByOrderId = async (id) => {
try{
const products = await Products.find({orderId:id}, (error, data) => {
if(error) console.log(error);
else
return data;
});
}
catch(err){
return 'error!!';
}
}
I understand that if the top-level call is async/await then all the nested calls should be awaited as well that I've tried to do.
What am I doing wrong here?
Get rid of all the then, and also of the callback in the DB query. Use only await. It will make the whole code a lot easier to reason about and it should also solve your issue as part of the restructuring.
In the example below I also added a loop over the orders, because you are fetching all orders, as array, but then your code was behaving as if it got only one. I also fixed the products loop.
However, the way you fetch products doesn't seem to be right anyway, see my comment below. I didn't fix it because I don't know how your database structure looks, so I don't know what the right way would be.
async function findCustomerOrder (id) {
const orders = await Order.find({ customerId: id })
for (const order of orders) {
console.log(`Order ID: ${order._id}`)
const products = await findProductsByOrderId(order._id)
for (const product of products) {
console.log(`Product ID: ${product._id}`);
}
}
}
async function findProductsByOrderId (id) {
// Note: I don't think this is right. It seems to find all
// products with the given ID - which will always be one -
// and that ID would be the _product_ ID and not the order ID.
return await Products.find({ _id: id })
}
Preemptive comment reply: The return await is intentional, and this is why.
You should remove the then and also the callback and you can do something like below
const findCustomerOrder = async(id) => {
try {
const orders = await Order.find({
custoemrId: id
});
if (orders) {
let promiseArray = [];
orders.forEach(order => {
promiseArray.push(findProductsByOrderId(order._id));
});
let results = await Promise.all(promiseArray);
return results;
}
return "order not found";
} catch (err) {
throw err;
}
}
const findProductsByOrderId = async(id) => {
try {
const products = await Products.find({
_id: id
});
return products;
} catch (err) {
throw err;
}
}

JavaScript, Node.JS : unable to update a variable in a loop

I am building an API via Node.js.
I have a loop that add values to an initially empty array, but the array stays empty. The following is my code.
get_doc_name = async (event) => {
const connection = await db.connection()
try {
const doc_ids = event.doc_ids
// array that has JSONs as element
// e.g. [ {1 : 'certificate.pdf'}, {2: 'report.jpeg'}]
let dict = []
await doc_ids.map(async function(id) {
const sql = "SELECT link FROM document WHERE id=?"
await connection.query(sql, id, async function (err, result) {
if (err) throw err
const link = await result[0].link
const doc_name = await link.split('/').pop()
await dict.push({
key: id,
value: doc_name
})
console.log(dict)
})
})
return dict
} catch (err) {
console.log(err.message)
} finally {
connection.end()
console.log('MySQL connection closed')
}
}
The function returns an empty array. However, it prints the updated array when console.log(dict) is executed. I need the function to return the updated array.
Moreover, when the function is called, the codes in the 'finally' statement are executed before the codes in 'try' statement. The program prints MySQL connection closed before it prints dict.
Any help is highly appreciated!
As you are retrieving data from DB, it is better to use serial Promise instead of parallel Promises to get data from DB. The way I do is in serial Promise.
It is a better practice to get data with SELECT link FROM document WHERE id in () instead of id= in your SQL because you don't need to make multiple I/O to the DB in your case.
However, I didn't change this part of logic.
Also, you should promisefy your connection.query and return a value from it.
I make it in a getResult() function.
get_doc_name = async event => {
try {
const connection = await db.connection();
try {
const doc_ids = event.doc_ids;
// array that has JSONs as element
// e.g. [ {1 : 'certificate.pdf'}, {2: 'report.jpeg'}]
let dict = [];
//Promises in serial here
for (const id of doc_ids) {
const result = await getResult(connection, id);
dict.push(result);
}
console.log(dict);
return dict;
} catch (err) {
console.log(err);
} finally {
await connection.end();
console.log("MySQL connection closed");
}
} catch (err) {
console.log(err);
}
};
function getResult(connection, id) {
return new Promise((resolve, reject) => {
const sql = "SELECT link FROM document WHERE id=?";
connection.query(sql, id, function (err, result) {
if (err) reject(err);
const link = result[0].link;
const doc_name = link.split("/").pop();
resolve({ key: id, value: doc_name });
});
});
}
get_doc_name()
.then(dict => console.log(dict))
.catch(console.log);

Async variable appearing empty in conditional logic, yet it is present in console logs

I'm a bit stumped as to how to correct this async issue. I have data vocab that I know I am successfully populating based on data from my database. However, at this time I can't quite figure out how to properly access that async data later in my function. Thoughts?
export const fsFetchLessonVocab = async (
language: string,
id: string,
includeTranslations?: boolean,
translationLanguage?: string
) => {
const lesson = await fsFetchLesson(language, id).then((res) => res);
const lessonContent = await lesson.contentID.get();
const vocab = [];
try {
await lessonContent.data().words.forEach((word) => {
word.get().then((res) => {
vocab.push({ ...res.data(), id: res.id });
});
});
return vocab;
} catch (error) {
console.log('Error retrieving content', error);
}
if (includeTranslations) {
console.log('inbound vocab', vocab); // YES - vocab array has content
if (vocab.length) {
// NO - vocab is an empty array.
const translations = await vocab.forEach((word) => {
console.log('word', word);
});
console.log('translations', translations);
}
}
}
more details
const vocab = [];
try {
await lessonContent.data().words.forEach((word) => {
word.get().then((res) => {
vocab.push({ ...res.data(), id: res.id });
});
});
return vocab;
} catch (error) {
console.log('Error retrieving content', error);
}
^ This block is working as expected. My content document is reached, and I iterate through the response array of words, finding each reference and pushing it into vocab.
if (includeTranslations) {
console.log('inbound vocab', vocab); // Populated array
if (vocab.length) {
// Empty array
const translations = await vocab.forEach((word) => {
console.log('word', word);
});
console.log('translations', translations);
}
}
^ the array vocab is appearing populated in the console.log(), however it's appearing empty within the if() block.
Continued:
I've found that whilte vocab shows and array with content, vocab.length shows 0 in the log.
try maybe this code:
try {
await Promise.all(
lessonContent.data().words.map(async word => {
const res = await word.get();
vocab.push({ ...res.data(), id: res.id });
}),
);
return vocab;
} catch (error) {
console.log('Error retrieving content', error);
}
if res.data() is async as well you need to await for that as well

Javascript function change variable value

I am currently developing signup page and I want to check if email address is already exist in the database.
var emailnum = email_num(`select * from contactinfo where email='${email}'`);
console.log(emailnum); //the output presents Promise { <pending> } not a num.
function sqlExecute(q) {
return new Promise((resolve, reject) => {
db.pool.query(q, function(err, result) {
if (err) {
console.log("ERROR ON " + q+"\n");
reject(err)
}
console.log("SUCCESS ON " + q);
resolve(result);
})
})
.catch(err => {
console.log(err);
})
}
//run check sql
async function email_num(tempquery){
var result = await sqlExecute(tempquery);
return result.rowCount;
}
I tried multiple ways but still could not figure it out.
I would appreciate any help TT
when I console.log, output is Always Promise { }.
I tried
var emailnum = email_num(`select count(*) as count from contactinfo where email='${email}'`)
.then((val)=>{
return val
};
console.log("number"+ emailnum);
The problem here is that you're not allowing the promise to resolve before attempting to retrieve the row count. When you create a function using async the return result is always going to be a promise. Here are a few solutions that will get you your desired result:
Solution 1: Use console.log to print the result of the promise after it has been resolved.
const email = 'something#gmail.com'
const numOfEmailsQuery = `SELECT * FROM contactinfo WHERE email = '${email}'`
email_num(numOfEmailsQuery)
.then(console.log)
.catch(console.error)
Solution 2: Use await inside of an async function to resolve the result of the promise and store it in a variable. Then print the result using console.log
async function printNumberOfEmails(email) {
const numOfEmailsQuery = `SELECT * FROM contactinfo WHERE email = '${email}'`
try {
const numOfEmails = await email_num(numOfEmailsQuery)
console.log(numOfEmails)
} catch (err) {
console.error(err)
}
}
const email = 'something#gmail.com'
printNumberOfEmails(email)
Hope that helps! Good luck!
First off, it's good practice to use prepared statements instead.
Also, you should just care about the count, so instead of select *, do select count(*) as count. Then you'll need check either the number of rows that result returned or the first index of rows, then the count property of that.
It should look something like this
function fetchEmailExists(email) {
const statement = 'select count(*) as count from contactinfo where email = $1';
return new Promise((resolve, reject) => {
db.pool.query(statement, [ email ], function(error, result) {
if (!error && result.rows.length > 0) {
resolve(result.rows[0]['count'] > 0);
} else {
reject(error);
}
});
})
.catch(error => {
console.log(error);
});
}
// Here is your call to fetchEmailExists
fetchEmailExists('test123#example.com')
.then(boolean => console.log(boolean));

Node.js - How to chain Promise in The right way

I recently moved from callback functions to promises in node.js. I want to preform async query to the DB (psql) in the most elegant way. I was wondering if the following code is the right way to do it or if I can chain for example the promises in a way of first().then(second).then(third).
function queryAll(req, res) {
searchQuery()
.then((rows) => {
console.log(rows);
totalQuery()
.then((total) => {
console.log(total);
});
});
res.json({"rows": rows, "total": total});
}
function searchQuery() {
return new Promise(function(resolve, reject) {
var rowData = { rows: {} };
pool.query('select age, sex from workers;', values, function(err, result) {
if(err) {
return console.error('error running query', err);
reject(err);
}
rowData.rows = result.rows;
resolve(rowData);
});
});
}
function totalQuery() {
return new Promise(function(reject, resolve) {
var totalData = { totals: {} };
pool.query('select sex, scores from workers group by sex;', values, function(err, result) {
if(err) {
return console.error('error running query', err);
reject(err);
}
totalData.totals = result.rows;
resolve(totalData);
});
});
}
var rowData = { rows: {} };
var totalData = { totals: {} };
First of all, these make no sense stored in variables since there's nothing else on the object. Just resolve with the rows directly instead.
return console.error('error running query', err);
Also, don't just console.log your errors. then accepts a second callback that executes on thrown errors or rejected promises. Throw this message in an error or reject with it instead. Also, I would leave logging to the consumer.
function queryAll(req, res) {
searchQuery()
.then((search) => {
console.log(rows);
totalQuery()
.then((total) => {
console.log(total);
});
});
res.json({"rows": rows, "total": total});
}
rows and total don't exist anywhere. Plus, by the time res.json executes, rows and total (assuming they come from inside the callbacks) won't exist yet since the whole sequence is async. You'll get undefined values as results.
I see little point in running searchQuery and totalQuery in sequence as they're not dependent on each other. It's better to run them parallel instead. Use Promise.all for that.
function queryAll(req, res) {
Promise.all([
searchQuery(),
totalQuery()
]).then(values => {
const rows = values[0];
const total = values[1];
res.json({"rows": rows, "total": total});
}, function(e){
// something went wrong
});
}
function searchQuery() {
return new Promise(function(resolve, reject) {
pool.query('select age, sex from workers;', values, function(err, result) {
if(err) reject(err);
else resolve(result.rows);
});
});
}
function totalQuery() {
return new Promise(function(reject, resolve) {
pool.query('select sex, scores from workers group by sex;', values, function(err, result) {
if(err) reject(err);
else resolve(result.rows);
});
});
}
You have a few issues in the code:
You return before executing reject()
There is an undefined rows variable (mismatch with search)
res.json is executed before the results are in.
The promises resolve to objects like { rows: rows }, but the main function seems to expect the plain numbers, not the objects. So let the promises just resolve to numeric values.
The second SQL is ambiguous since the second field is not aggregated and does not appear in the group by clause either. Assuming you want to sum the scores, use sum().
The second query is only launched after the first one has returned results, but this can be done in parallel
You have very similar code repeated. Try to reuse code and make the SQL statement an argument.
Here is how I would suggest to do it:
function queryAll(req, res) {
return Promise.all([searchQuery(), totalQuery()]).then(([rows, total]) => {
console.log('rows', rows);
console.log('total', total);
// Make sure to only access the promised values in the `then` callback
res.json({rows, total});
});
}
function searchQuery() {
return promiseQuery('select age, sex from workers;');
}
function totalQuery() {
// When you group, make sure to aggregate:
return promiseQuery('select sex, sum(scores) as scores from workers group by sex;');
}
function promiseQuery(sql) { // reusable for other SQL queries
return new Promise(function(resolve, reject) {
pool.query(sql, values, function(err, result) {
if(err) {
// Do not return before calling reject!
console.error('error running query', err);
reject(err);
return;
}
// No need for a variable or object, just resolve with the number of rows
resolve(result.rows);
});
});
}
The most elegant solution would be via pg-promise:
function queryAll(req, res) {
db.task(t => {
return t.batch([
t.any('SELECT age, sex FROM workers', values),
t.any('SELECT sex, scores FROM workers GROUP BY sex', values)
]);
})
.then(data => {
res.json({rows: data[0], total: data[1]});
})
.catch(error => {
// handle error
});
}
And that's everything. You don't have to reinvent promise patterns for working with the database, they are all part of the library already.
And if your queries have a dependency, see: How to get results from multiple queries at once.
Or if you prefer ES6 generators:
function queryAll(req, res) {
db.task(function* (t) {
let rows = yield t.any('SELECT age, sex FROM workers', values);
let total = yield t.any('SELECT sex, scores FROM workers GROUP BY sex', values);
return {rows, total};
})
.then(data => {
res.json(data);
})
.catch(error => {
// handle error
});
}
And with the ES7's await/async it would be almost the same.
First of all there are some errors in your code, you have to place the reject before the return, otherwise it will be never called, and create a dangling promise:
function searchQuery() {
return new Promise(function(resolve, reject) {
var rowData = {
rows: {}
};
pool.query('select age, sex from workers;', values, function(err, result) {
if (err) {
reject(err);
console.error('error running query', err);
} else {
rowData.rows = result.rows;
resolve(rowData);
}
});
});
}
Beside that you should not nest the Promises when ever possible.
So it should be:
function queryAll(req, res) {
var result = {};
searchQuery()
.then((search) => {
console.log(search);
result.rows = search;
return totalQuery();
})
.then((total) => {
result.total = total;
console.log(total);
});
}
The res.json has to be called in the then part of the Promise:
function queryAll(req, res) {
var result = {};
searchQuery()
.then((search) => {
console.log(search);
result.rows = search;
return totalQuery();
})
.then((total) => {
result.total = total;
console.log(total);
})
.then(() => {
res.json({
"rows": result.rows,
"total": result.total
});
});
}
If your queryAll is called as e.g. middleware of express, then you should handle the catch case within queryAll:
function queryAll(req, res) {
var result = {};
searchQuery()
.then((search) => {
console.log(search);
result.rows = search;
return totalQuery();
})
.then((total) => {
result.total = total;
console.log(total);
})
.then(() => {
res.json({
"rows": result.rows,
"total": result.total
});
})
.catch( err => {
res.status(500).json({error: 'some error'})
});
}
For postgress I would suggest to use pg-promise instead of using a callback style library and wrapping it into promises yourself.
You could simplify the code if you use a library like bluebird:
const bPromise = require('bluebird')
function queryAll(req, res) {
bPromise.all([
searchQuery(),
totalQuery()
])
.spread((rows, total) => {
res.json({
"rows": rows,
"total": total
});
})
.catch(err => {
res.status(500).json({
error: 'some error'
})
});
}
With nsynjs your logic may be coded as simple as this:
var resp = {
rows: dbQuery(nsynjsCtx, conn, 'select age, sex from workers', values1).data,
total: dbQuery(nsynjsCtx, conn, 'select sex, scores from workers group by sex', values2).data
};
Please see example of multiple sequential queries here: https://github.com/amaksr/nsynjs/tree/master/examples/node-mysql

Categories