promise logic with sql in nodejs when making calls to database - javascript

I have the code below that is reliant on two api calls to two different tables. It first gets data from database, gets an id from there, makes another call to another table, gets data from there and appends it to each value from the first call. The code works fine in theory but I just need to be able to resolve the final product by creating a chain of events. The final resolve should wait for the entire code to finish executing and then return the value of first_call.
Here is my code. Let me know how I can modify it.
Updated code
function getUsers() {
return new Promise((resolve, reject) => {
var sql = `SELECT p.customer, p.fname, p.lname, p.email, p.user, c.name AS organizationName, c.email AS organizationEmail c.did AS phoneNumber FROM people AS p LEFT JOIN customers AS c ON c.id = p.customer`;
console.log(sql)
con.query(sql, function (err, result) {
if (err) throw err; 
resolve(result); 
});
});
}
Old code
function getUsers() {
return new Promise((resolve, reject) => {
var first_call = []
var sql = `SELECT customer, fname, lname, email, user FROM people`;
con.query(sql, function (err, result) {
if (err) throw err;
first_call = result
});
for (let i = 0; i < first_call.length; i++) {
var sql2 = `SELECT name, email, did FROM customers WHERE id = ${first_call[i].customer}`;
con.query(sql2, function (err, result2) {
first_call[i].organizationName = result2[0].name;
first_call[i].organizationEmail = result2[0].email;
first_call[i].phoneNumber = result2[0].did;
});
}
resolve(first_call);
});
}

The simplest solution would be to combine the two queries with a JOIN:
SELECT p.customer, p.fname, p.lname, p.email, p.user, c.name AS organizationName, c.email AS organizationEmail, c.did AS phoneNumber
FROM people AS p
LEFT JOIN customers AS c ON c.id = p.customer
But if you really want two queries, here's how to rewrite your promise code using async and await.
You should also use a parameter in the SQL rather than substituting a variable.
async function getUsers() {
var first_call = []
var sql = `SELECT customer, fname, lname, email, user FROM people`;
first_call = await con.query(sql)
var sql2 = `SELECT name, email, did FROM customers WHERE id = ?`;
for (let i = 0; i < first_call.length; i++) {
let result2 = await con.query(sql2, [first_call[i].customer]);
first_call[i].organizationName = result2[0].name;
first_call[i].organizationEmail = result2[0].email;
first_call[i].phoneNumber = result2[0].did;
}
return first_call;
}

Related

Best practice for waiting on nested thens

I have a scenario where I have three tables in a PostgreSQL database, users, orgs, and users_orgs, which links the first two. I use Express and pg to handle the DB calls. Now, if I want to, say, attach a list of org records to a user record, I want to perform one query for the user record, one for all of the users_orgs associated with that user, and then one for each of those records to get the orgs.
When I do this, I end up with something like
const user_query = `SELECT * FROM users WHERE id=${id}`;
pg.query(user_query)
.then((user_result) => {
const users = user_result.rows;
users.map((user) => {
user.orgs = [];
const users_orgs_query = `SELECT org_id FROM users_orgs WHERE user_id = '${user.id}'`;
pg.query(users_orgs_query)
.then((users_orgs_result) => {
return new Promise((res, rej) => {
const users_orgs = users_orgs_result.rows;
let c = 0;
users_orgs.map((user_org) => {
const orgs_query = `SELECT * FROM orgs WHERE id = '${user_org.org_id}'`;
pg.query(orgs_query)
.then((r) => {
const orgs = r.rows;
user.orgs.push(orgs[0]);
c += 1;
if (c >= users_orgs.length) {
res(user);
}
});
});
});
})
.then((u) => {
res.status(200).json(u);
});
});
Now, this works, but I feel confident that counting into my map is not a good idea. Clearly, I could replace the inner map with a for loop and just count and resolve the promise that way as well, but that feels like something I would do in C (which is where I normally work) and that feels like cheating.
However, I need the resolve to happen after the last element maps because otherwise, I will respond to the request before adding all the orgs to the user. Surely this is not uncommon, but I feel like I am not seeing anything related when I search, which leads me to believe that I'm searching poorly and/or thinking about this all wrong.
There may even be a SQL query-based solution to this type of thing, and that's great, but I would still be curious if there is a way to wait for a loop of nested promises to resolve in an elegant manner.
To make it clear, the actual question is, is there a way to know that all of the inner promises have been resolved without having to actually count them all, as I do here?
You can wait for all the promises to finish by using Promise.all. This function accepts an array of promises and returns a Promise by itself, which will resolve when all given promises have successfully been resolved.
Use this is in combination map method of the array to return an array of promises.
async function getUsersWithOrgs(id) {
const user_query = `SELECT * FROM users WHERE id=${id}`;
const { rows: users } = await pg.query(user_query)
return Promise.all(users.map(async user => {
const users_orgs_query = `SELECT org_id FROM users_orgs WHERE user_id = '${user.id}'`;
const { rows: users_orgs } = await pg.query(users_orgs_query);
const orgs = await Promise.all(users_orgs.map(async user_org => {
const orgs_query = `SELECT * FROM orgs WHERE id = '${user_org.org_id}'`;
const { rows: orgs } = await pg.query(orgs_query);
return orgs[0];
}));
return {
...user,
orgs
};
}));
}
getUsersWithOrgs(id).then(users => {
res.status(200).json(users);
});
You need to use async, await and for the inner promises you can use Promise.all
awaiting individually query will be un-optimal.
You can structure your code like (taking the innermost query as an example):
const orgs_query = `SELECT * FROM orgs WHERE id = '${user_org.org_id}'`;
await Promise.all(user_orgs.map((user_org) => {
pg.query(orgs_query)
.then((r) => {
const orgs = r.rows;
user.orgs.push(orgs[0]);
});
}));
// Return the response as you like from the users object
The pq.query returns a Promise from the mapped function. The outer Promise.all will collect these promises and await till all of them return.
This way you don't need to nest.
Async version, btw your code is a little complex, so maybe not work.
If not working, please comment:
const user_query = `SELECT * FROM users WHERE id=${id}`;
const user_result = await pg.query(user_query);
const users = result.rows;
for (let i = 0; i < users.length; i++) {
const user = users[i];
user.orgs = [];
const users_orgs_query = `SELECT org_id FROM users_orgs WHERE user_id = '${user.id}'`;
const users_orgs_result = await pg.query(users_orgs_query);
const users_orgs = users_orgs_result.rows;
for (let j = 0; j < users_orgs.length; j++) {
const user_org = users_orgs[j];
const orgs_query = `SELECT * FROM orgs WHERE id = '${user_org.org_id}'`;
const r = await pg.query(orgs_query);
const orgs = r.rows;
user.orgs.push(orgs[0]);
if (c == users_orgs.length - 1) {
res.status(200).json(u);
}
}
}

Express passing arrays into sql (express/mssql/react)

I am trying to upload two arrays into my sql database.
This is what I have come up with.(this is my server.js using a endpoint from my client side)
My express
app.post("/post-question-answers", async (req, res) => {
console.log("!called");
try {
await sql.connect(config);
// create Request object
var request = new sql.Request();
let results = req.body.results;
let questions = [];
let answers = [];
results.forEach(element => questions.push(element.question));
results.forEach(element => answers.push(element.answer));
for (var i = -1; i < results.length; i++) {
request.input("Question", sql.VarChar, questions[i]);
request.input("Answer", sql.VarChar, answers[i]);
request.execute("dbo.AddQuestionResponses", function(err, recordset) {
if (err) console.log(err);
// send records as a response
res.json(recordset);
});
}
} catch (e) {
console.log(e);
}
});
My sql stored procedrue
alter procedure AddQuestionResponses
#Question nvarchar (50),
#Answer nvarchar (50)
as
insert into QuestionResponses(QuestionWhenAnswered, QuestionResponse)
values (#Question ,#Answer )
However this throws
RequestError: The parameter name Question has already been declared. Parameter names must be unique
I believe this is because
request.input("Question", sql.VarChar, questions[i]);
request.input("Answer", sql.VarChar, answers[i]);
need to be unique and as they are in a for loop they are repeated within the statement. Is there a way in which I can make this a valid transaction with the database and so that these are unique.
Thankyou for your time :)
I solved this issue by putting
var request = new sql.Request();
within the for loop.

Async functions in for loops javascript

I am not experienced with async functions and I would like to perform a request in a for loop. Here's my code:
app.route('/friendlist').post((req, res) => {
var body = req.body;
var list = "";
con.query(`SELECT * FROM player_friends WHERE main_user_id = '${body.player_id}'`, (err, row, fields) => {
if (err) throw err;
async function queryOutUserData(data) {
var rows = await new Promise((resolve, reject) => {
con.query(`SELECT * FROM players WHERE player_id = '${data.player_id}'`, (error, player, field) => {
if (error) {
console.log(error);
return reject(error);
}
resolve(player);
});
});
rows.then(message => {
return message
});
}
for (var i = 0; i <= row.length; i++) {
console.log(row[i].main_user_id);
var result = await queryOutUserData(row[i]);
list = list + ";" + result[0].player_id + ":" + result[0].player_username;
}
console.log(list);
return list;
});
});
Actually here's the full problem: I did some debugging and apparently value i in for loop increases before the promise is resolved. Also as I mentioned I am not familiar with async functions, could you provide me a descriptive resource about how promises and async functions work?
Thanks
NOTE: For better indentation, here's the code: https://hastebin.com/fovayucodi.js
Instead of using async/await I suggest doing everything in one query using WHERE IN rather than one query per player. See if the following fits your needs:
app.route('/friendlist').post((req,res) => {
var body = req.body;
var list = "";
con.query(`SELECT * FROM player_friends WHERE main_user_id = '${body.player_id}'`, (err, row, fields) => {
if (err) throw err;
const playerIds = row.map(player => player.player_id);
con.query(`SELECT * FROM players WHERE player_id IN ${playerIds}`, (error, players, field) => {
for (let player of players) {
list += `;${player.player_id}:${player.player_username}`;
}
});
console.log(list);
return list;
});
});
If you await a promise, it evaluates to the result of that promise, so rows is not a promise, it's the result. So this:
rows.then(message => {return message});
Doesn't make much sense, just do:
return message;
Also, you have an await inside of a regular function, thats a syntax error.
Additionally return list; doesn't do much (if that is express), you might want to return res.json({ list });.
: I did some debugging and apparently value i in for loop increases before the promise is resolved.
I doubt that you can debug code if you can't actually run it because of the syntax errors.
try to use for-of instead just a for.
something like this:
Async Function:
async function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
return
}, 1000)
})
}
Here another function using for and waiting for the finish of loop
async function myFunction() {
const data = [1,2,3,4,5,6,7,8]
for(let i of data) {
const value = await test();
console.log(value)
}
console.log("finish");
}

How to use chaining promise together with for loops of arrays?

So, I have this code. I'm trying to debug it in forever. I dont know why it is returning error. I might have some element of promise I forgot or I have something wrong with my array.push 'cause when I look at the log, it throws error on line where I push some objects into array.
Here is my code so far:
router.post('/inventory/product/stocks/add/(:id)', authenticationMiddleware(), function(req, res, next) {
const db = require('../db.js')
var product_no = req.params.id
var cog = req.body.cog
var size_slug = req.body.size_slug
var size_name = req.body.size_name
var rowinserted = 0
var initial_stock = req.body.initial_stock
var stock_id = new Array
var batch_id = new Array
var stock = new Array
var batch = new Array
new Promise(function(resolve, reject) {
console.log('one');
// Getting product product_slug for product_sku
let sql = `SELECT product_slug
FROM inventory_tbl
WHERE product_no = ?`
db.query(sql, [req.params.id], (error, results, fields) => {
if (error) {
throw error;
} else {
var product_slug = results[0].product_slug
resolve(product_slug)
}
})
})
.then(function(value) {
console.log('two');
// Insert product sizes together with its initial stock
for (var x = 0; x < size_slug.length; x++) {
var product_sku = value + size_slug[x]
var slug = size_slug[x]
var name = size_name[x]
var initial_stock = initial_stock[x]
console.log(product_sku);
if (size_slug[x] != '') {
stock.push({
product_sku: product_sku,
product_no: product_no,
size_slug: slug,
size_name: name,
total_stock: initial_stock,
available_stock: initial_stock
})
}
console.log(stock);
}
for (var x = 0; x < size_slug.length; x++) {
var product_sku = value + size_slug[x]
var initial_stock = initial_stock[x]
if (size_slug[x] != '') {
batch.push({
product_no: product_no,
product_sku: product_sku,
production_date: mysql.raw('CURRENT_TIMESTAMP'),
batch_cog: cog,
initial_stock: initial_stock,
stock_left: initial_stock
})
}
console.log(batch);
}
return value
})
.then(function(value) {
console.log('three');
// Insert rows to product_tbl and stock_tbl
for (var i = 0; i < stock.length; i++) {
let sql = `INSERT INTO product_tbl(product_sku, product_no, size_slug, size_name, total_stock, available_stock) VALUES (?, ?, ?, ?, ?, ?)`
db.query(sql, [stock[i].product_sku, req.params.id, stock[i].size_slug, stock[i].size_name, stock[i].total_stock, stock[i].available_stock], (error, results, fields) => {
if (error) throw error
db.query(`SELECT LAST_INSERT_ID() AS id;`, (error, results, fields) => {
stock_id[i] = results[0].id
})
})
sql = `INSERT INTO stocks_tbl(product_no, product_sku, production_date, batch_cog, initial_stock, stock_left) VALUES (?, ?, CURRENT_DATE, ?, ?, ?)`
db.query(sql, [req.params.id, batch[i].product_sku, batch[i].batch_cog, batch[i].initial_stock, batch[i].stock_left], (error, results, fields) => {
if (error) throw error
db.query(`SELECT LAST_INSERT_ID() AS id;`, (error, results, fields) => {
batch_id[i] = results[0].id
})
})
rowsupdated++
}
return value
})
.then(function(value) {
console.log('four');
// Render the web page
if (rowinserted != sizeslug.length) {
req.flash('error', error)
res.redirect('/admin/inventory/product/stock/add/' + req.params.id)
} else {
req.flash('success', 'Data added successfully!')
res.redirect('/admin/inventory/product/stock/add/' + req.params.id)
}
})
.catch(function(error) {
console.log('error');
// Error handler
for (var i = 0; i < rowinserted; i++) {
let sql = `DELETE FROM product_tbl WHERE product_sku = ?`
db.query(sql, [stock_id[i]], (error, results, fields) => {
if (error) throw error
})
sql = `DELETE FROM stocks_tbl WHERE product_sku = ?`
db.query(sql, [batch_id[i]], (error, results, fields) => {
if (error) throw error
})
}
res.redirect('/admin/inventory/product/stock/add/' + req.params.id)
})
})
My log returns:
one
two
error
Edit: The process stops (I'm not sure the specific line but according to the log output) after console.log('two') because I tried putting some log as well after the for loops but they don't proceed there. It just go to the .catch/error.
Instead of outputting a string in console.log('error'); dump out an actual error object that you receive in the catch handler. It will give additional details of why and where it fails. I suspect that the code after console.log('two'); throws an exception and then you unintentionally swallow it below.
Consider splitting your code into separate thematic functions. That way you will be able to maintain and spot the errors (or typos) much easier.
Looking at the output, i can see that console.log(product_sku); this is not getting printed. So, actually the problem is var initial_stock = initial_stock[x]. You have declared the local variable(to your then callback function) with same name as global variable(to your route.post callback functions) and now your global initial_stock variable is masked with local one, which is not an array (actually is undefined). So try changing the variable name to something else in your then block and see if problem disappear.
Hope this helps.

Force loop to wait for mysql insert before moving to next iteration

I have a loop that runs and inserts records into a mysql database for each iteration. The problem is that I think async is screwing up the writing to the db. If i have a 10 iteration loop, only 8 of the inserts are being recorded (for example).
I'm trying to find a way to make the loop wait for the update to occur before going on to the next iteration of the loop. Here's the code:
var queryString1 = "insert into expense_summary "+
"(user_id, customer_id, expense_date, category, cost) values (?,?,?,?,?)"
connection.query(queryString1, [
detail[0].employee,
detail[0].customer,
detail[0].date,
detail[0].category,
detail[0].total], function(err, result) {
var summaryId = result.insertId;
console.log("This is the summary ID" + summaryId);
var data;
for (i = 0; i < detail.length; i++) {
var queryString = "insert into expense_detail "+
"(expense_summary_id, expense_date, line_id, item_name, description, cost_per, quantity, tax, user_id, customer_id, expense_category_id, ad_hoc, reimbursable) "+
"values (?,?,?,?,?,?,?,?,?,?,?,?,?)";
connection.query(queryString, [
summaryId,
detail[i].date,
detail[i].line,
detail[i].item,
detail[i].description,
detail[i].cost,
detail[i].quantity,
detail[i].tax,
detail[i].employee,
detail[i].customer,
detail[i].category,
detail[i].adHoc,
detail[i].reimbursable], function(err, result) {
if(err){
throw err;
} else{
console.log(result);
}
});
}
});
You can use async/await if you using latest node.js > 8.
(async () => {
for (let i = 0; i < 10; i++) {
console.log(i);
await new Promise (resolve => setTimeout (resolve, 500))
}
})();
Wrap your whole function in the promise and replace that with timeout promise.
Or you can use a modern for … of loop instead, in which await will work as expected:
async function query() {
for (let d of detail) {
const contents = await your_query_promise();
}
}
It looks the typical problem with variables and scopes.
Just try changing these 2 lines:
for (let i=0; i<detail.length; i++){
let queryString = "insert into expense_detail "+
"(expense_summary_id, expense_date, line_id, item_name, description, cost_per, quantity, tax, user_id, customer_id, expense_category_id, ad_hoc, reimbursable) "+
"values (?,?,?,?,?,?,?,?,?,?,?,?,?)";
Declaring varibales with let you avoid sharing the variable values among the different iterations

Categories

Resources