Waiting for async function in for loop - node.js

I need to wait for an async method (a call to my database) for every object in an array. Right now I have a for loop going through the array calling the async method on each object. The async function is successful but I need to wait for every async call to finish before moving on. Doing some research I have found that Promises combined with await or other methods are the solution to my problem but I haven't been able to figure it out. Here is my code I have right now.
Here is my class with the async function
Vacation : class Vacation {
constructor(id, destination, description, attendee_creator_id) {
this.id = id;
this.destination = destination;
this.description = description;
this.attendee_creator_id = attendee_creator_id;
this.creator = undefined;
this.votes = undefined;
}
find_creator(pool){
return new Promise(function (resolve, reject) {
var self = this;
var query = "SELECT * FROM vacation_attendee WHERE id = " + self.attendee_creator_id;
pool.query(query, function(error, result) {
if (error) {
console.log("Error in query for vacation creator " + error);
return reject(error);
}
var creator = new attendee.VacationAttendee(result.rows[0].full_name, result.rows[0].email, result.rows[0].password_hash);
self.creator = creator;
console.log("creator found ----> " + self.creator.full_name + "in " + self.destination);
resolve(true);
});
})
}
here is how i'm calling the async function
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
callback(error);
}
var all_complete = loop_through_vacations(vacations_query_result.rows, pool);
callback(null, all_complete);
});
}
async function loop_through_vacations(vacations_incomplete, pool) {
var all_vacations = [];
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
vacation_at_index.find_creator(pool)
.then(()=> {
all_vacations.push(vacation_at_index);
})
.catch(error=> {
console.log(error);
});
console.log("passed vacation " + vacation_at_index.destination);
}
return await Promise.all(all_vacations);
}

You do it in a wrong way, you don't wait anything in you for-loop.
return await Promise.all(all_vacations); does not work, because all_vacations is not a Promise array.
In your case, we have many way to do this, my way just is a example: Create a array to store all promises what have been created in your for-loop, then wait until the all promises finished by Promise.all syntax.
async function loop_through_vacations(vacations_incomplete, pool) {
var all_vacations = [];
var promises = []; // store all promise
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
promises.push( // push your promise to a store - promises
vacation_at_index.find_creator(pool)
.then(() => {
all_vacations.push(vacation_at_index)
})
.catch((err) => {
console.log(err); // Skip error???
})
);
}
await Promise.all(promises); // wait until all promises finished
return all_vacations;
}
I don't know why you use async/await mix with callback style, I recommend async/await for any case:
Mix style:
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, async function(error, vacations_query_result) { // async
if (error) {
console.log("error in vacations query " + error);
return callback(error); // return to break process
}
var all_complete = await loop_through_vacations(vacations_query_result.rows, pool); // await
callback(null, all_complete);
});
}
async/await:
async function get_all_vacations() {
var sql_vacations_query = "SELECT * FROM vacation";
var rows = await new Promise((resolve, reject) => {
pool.query(sql_vacations_query, function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
return reject(error);
}
resolve(vacations_query_result.rows);
});
})
var all_complete = await loop_through_vacations(rows, pool); // await
return all_complete;
}

For me, you can try to reformat your SQL queries, the code will be cleaner and it will take less time to make a big query once than multiple queries.
If I understand joins in your database:
SELECT *
FROM vacation_attendee AS attendee
INNER JOIN vacation ON vacation.attendee_creator_id = attendee.id
-- WHERECLAUSEYOUWANT
Then:
async function get_all_vacations(callback){
const allComplete = await makeThatBigQuery(); // + treat data as you want
callback(null, allComplete);
}
In your definition of get_all_vacations you don't wait all_complete, so the callback is called before all_complete is ready

This is how the await/promises part of the code should look like:
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, async function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
callback(error);
}
var all_complete = await loop_through_vacations(vacations_query_result.rows, pool);
callback(null, all_complete);
});
}
async function loop_through_vacations(vacations_incomplete, pool) {
const promises = [];
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
promises.push(vacation_at_index.find_creator(pool));
}
return await Promise.all(promises);
}
Ideally you should be removing all the callbacks(not sure if asyn/await is supported for pool.query methods) and change the whole code to async/await style. Also you should be resolving the promise from 'find_creator' with resolve(creator), so that the value is obtained when the promise is resolved.

Related

Can I execute an Firestore query in .then callback of another query?

I am implementing a Cloud Function where I am executing several queries.
let rating_subcollection = await admin.firestore().collection("restaurants_collection").doc(vendor_id).collection('ratings').where('uID', '==', userId)
.get()
.then(async function (data) {
if (data.empty) {
let restaurants_collection = admin.firestore().collection("restaurants_collection").doc(vendor_id);
await admin.firestore().runTransaction(async (transaction) => {
const restDoc = await transaction.get(restaurants_collection);
// Compute new number of ratings
const newNumRatings = restDoc.data().noRat + 1;
// Compute new average rating
const oldRatingTotal = restDoc.data().rat * restDoc.data().noRat;
const newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;
// Update restaurant info
transaction.update(restaurants_collection, {
rat: newAvgRating,
noRat: newNumRatings
});
})
}
}).catch(error => {
return "Couldnt update the rating: " + error;
})
So, as you can see I am only executing the transaction IF data is empty and I have set async in the then() callback. Is this the right way to do?!
I found this example, where is explained on detail how get a collection using an async function, for example if you want to get a collection:
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then(function (doc) {
if (doc.exists) return doc.data().text;
return Promise.reject("No such document");
}};
}
Or using await it inside an async function in a try/catch block, if you like that better:
async function doSomething() {
try {
let text = await getValues('configuration','helpMessage');
console.log(text);
} catch {
console.log("ERROR:" err);
}
}

Return a value from function inside promise

I am trying to return the array shiftInfo from the function csData() within a promise.
function crewsense(){
var request = CS.find({});
request
.then(result => {
var created = result[0].created,
currentTime = moment(),
diff = (currentTime - created);
if(diff < 84600000){
console.log("Current Token is Valid");
var access_token = result[0].access_token;
console.log('Obtaining Crewsense Shift Data');
return access_token
}else{
console.log("Current Token is invalid. Updating Token");
csToken();
}
}).then(access_token => {
csData(access_token) //I am trying to get this function to return async data.
}).then(shiftInfo => { //I want to use the data here.
})
Here is the csData function:
function csData(csKey) {
const dayURL = {
method: 'get',
url: 'https://api.crewsense.com/v1/schedule?start='+today+'%2007:30:00&end='+tomorrow+'%2007:30:00',
headers:{
Authorization: csKey,
}
}
const request = axios(dayURL)
request
.then(result => {
var shiftInfo = [];
var thisShift = [];
var onDuty = result.data.days[moment().format("YYYY-MM-DD")].assignments;
thisShift.push(result.data.days[moment().format("YYYY-MM-DD")].day_color);
var persons = [];
var i = 0;
for(var i=0; i<onDuty.length; i++){
let station = onDuty[i].name
for(var x=0; x<onDuty[i].shifts.length; x++){
var person = {
name: onDuty[i].shifts[x].user.name,
position: onDuty[i].shifts[x].qualifiers[0].name,
station: station
}
persons.push(person);
}
}
shiftInfo = [{thisShift}, {persons}];
// console.log(shiftInfo)
return shiftInfo
})
.catch(error => console.error('csData error:', error))
}
I have attempted assigning var shiftInfo = csData(access_token) w/o success and several other ways to call the csData function. I have attempted reading other like problems on here and I have just ended up confused. If someone can point me in the right direction or please point out the fix I might be able to get it to click in my head.
I appreciate everyone's time.
Thanks!
Whatever you return inside a then, will be passed to the next then callback. If you return a Promise, the result of the promise will be sent to the next then callback:
new Promise((resolve) => {
// We resolve to the value we want
resolve("yay");
}).then((value) => {
// In the first then, value will be "yay"
console.log("First then:", value);
// Then we return a new value "yay x2"
return value + " x2";
}).then((value) => {
// In this second then, we received "yay x2"
console.log("Second then:", value);
// Then we return a promise that will resolve to "yay x2 again"
return new Promise((resolve) => {
setTimeout(() => {
resolve(value + " again");
}, 1000);
});
}).then((value) => {
// After a second (when the returned Promise is resolved) we get the new value "yay x2 again"
console.log("Third then:", value);
// And now we return a Promise that will reject
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("wtf"));
}, 1000);
});
}).catch((error) => {
// This catch on the whole promise chain will catch any promise rejected
console.log(error.toString());
});
So simply csData must return the promise is creating, and you need to return that promise to the then callback you want:
[...]
}).then(access_token => {
return csData(access_token) //I am trying to get this function to return async data.
}).then(shiftInfo => { //I want to use the data here.
console.log(shiftInfo);
}).catch((err) => {
// Whatever...
});
function csData(csKey) {
[...]
return request.then(result => {
[...]
}
Because you are returning a promise, I recommend you to add the catch outside csData and add it to the promise chain you have before.

Bulk update to Postgres with node js performance issue

I'm facing performance issue while trying to do bulk update in PostgresDB. It's taking more than 180 seconds to update around 23000 records. PFB the code. I'm using pg-promise library. Is there anything I could do to improve the performance?
const pgp = require('pg-promise')();
const postgresDBConfig = {
host: Config.postgresDBHost,
port: Config.postgresDBPort,
database: Constants.postgresDBName,
user: Config.postgresDBUser,
password: 'pswd'
};
export async function getTransactionDetails(): Promise<any> {
return new Promise<any>(async function (resolve, reject) {
try {
let db = pgp(postgresDBConfig);
db.connect();
let query = "SELECT * FROM table_name";
db.any(query)
.then(data => {
console.log("Executed successfully::");
resolve(data);
})
.catch(error => {
console.log('ERROR:', error);
})
} catch (error) {
log.error("Error::" + error);
throw error;
}
});
}
export async function updateStatus(result: any, status: string) {
try {
let db = pgp(postgresDBConfig);
//db.connect();
let updateData = [];
_.forEach(result, function (row) {
let updateInfo = {};
updateInfo["sessionid"] = row.sessionid;
updateInfo["status"] = status;
updateData.push(updateInfo);
});
console.log("updateData::" + updateData.length);
const tableName = new pgp.helpers.TableName('table_name', 'schema_name');
let columnset = new pgp.helpers.ColumnSet(['?sessionid', 'status'], { table: tableName });
let update = pgp.helpers.update(updateData, columnset);
db.none(update).then(() => {
console.log("Updated successfully");
})
.catch(error => {
console.log("Error updating the status" + error);
});
}
catch (error) {
log.error("Error in function updateStatus::" + error);
throw error;
}
}
The code exhibits problems all over the place
You should initialize the database object only once
You should not use db.connect() at all, which you also use incorrectly for the async code
You again incorrectly use async block, skipping await, so it doesn't execute correctly.
You do not append any UPDATE logic clause, so it is updating everything all over again, unconditionally, which may be resulting in a delayed mess that you're in.
Here's an improved example, though it may need some more work from your side...
const pgp = require('pg-promise')();
const postgresDBConfig = {
host: Config.postgresDBHost,
port: Config.postgresDBPort,
database: Constants.postgresDBName,
user: Config.postgresDBUser,
password: 'pswd'
};
const db = pgp(postgresDBConfig);
const tableName = new pgp.helpers.TableName('table_name', 'schema_name');
const columnSet = new pgp.helpers.ColumnSet(['?sessionid', 'status'], {table: tableName});
export async function getTransactionDetails(): Promise<any> {
try {
const res = await db.any('SELECT * FROM table_name');
console.log('Executed successfully::');
return res;
} catch (error) {
console.log('ERROR:', error);
throw error;
}
}
export async function updateStatus(result: any, status: string) {
try {
let updateData = [];
_.forEach(result, row => {
let updateInfo = {};
updateInfo["sessionid"] = row.sessionid;
updateInfo["status"] = status;
updateData.push(updateInfo);
});
console.log('updateData::', updateData.length);
const update = pgp.helpers.update(updateData, columnSet) +
' WHERE v.sessionid = t.sessionid';
await db.none(update);
console.log('Updated successfully');
}
catch (error) {
console.log('Error in function updateStatus:', error);
throw error;
}
}

async save multiple document with mongoose

i am updating 2 documents using mongoose.save(), but i think the way I am doing is is not safe, as far as i know i need to use async to make sure all documents are being executed
// array containing the 2 documents from db
let schedules
let newItem = {
isActive: room.isActive,
name: room.roomname
};
// adding new items to nested array
schedules[0].rooms.push(newItem);
schedules[1].rooms.push(newItem);
// saving / updating documents
var total = schedules.length,
result = [];
function saveAll() {
var doc = schedules.pop();
doc.save(function(err, saved) {
if (err) throw err; //handle error
result.push(saved);
if (--total) saveAll();
else {
// all saved here
res.json(result);
}
});
}
saveAll();
any explanation how to do it correctly
We can use promise.all for this but we need to change your save function to promise based function
...
var total = schedules.length,
result = [];
function saveAll() {
const promises = schedules.map(schedule => save(schedule));
return Promise.all(promises)
.then(responses => {
// all saved processes are finished
res.json(responses);
})
}
// convert callback `save` function to promise based
function save(doc) {
return new Promise((resolve, reject) => {
doc.save((err, saved) => {
if (err) {
reject(err);
}
resolve(saved);
});
});
}
If you can use async/await we can make saveAll function cleaner
async function saveAll() {
const promises = schedules.map(schedule => save(schedule));
const responses = await Promise.all(promises);
res.json(responses);
}
Hope it helps
Use Promises :
doc1.save().exec().then(
data => {
doc1.save().exec().then(
data2 => console.log(data2)
).catch(err2 => console.log(err2))
}
).catch(err1 => console.log(err1))

How to implement nested query in sqlite3

So i have this 2-layer query in node.js, each query could return multiple results. My code actually just ignores that for now. This is the best i can get, it seems working.
How to correct it please, i don't know how to callback for the 2nd one.
Also the db.close() is always called before the 2nd query finishes, even i have serialize().
var getInfo1Db = function(callback) {
var db = new sqlite3.Database("DB.sqlite3");
var cnt = 0;
var info1JsonObj = [];
db.all("select * from Info1DB",
function(err, rows) {
db.serialize(function() {
for(var ii=0, len=rows.length; ii<len; ii++) {
var t2 = rows[ii].info1;
var doorId = ...
db.all("select * from DoorDB where ObjectID=" + doorId,
function(err, row2) {
if(err) {
} else {
var doorName = row2[0]...
var info1JsonElem = {
"DoorName" : doorName
};
info1JsonObj.push(info1JsonElem);
cnt++;
if(cnt === rows.length) {
callback(null, info1JsonObj);
}
}
}
); // for the only door info based on door id
} // for each row of info1
db.close(); // why this finishes before the 2nd db.all
} ); // end of serialize
});
};
You can't implement nested query in sqlite3's normal way. ( I mean you even can't do it in the callback hell way, because the sqlite3 need to close the connection before another query called. otherwise you will always got error)
You have to use Promise, async and await to do this.
( it's worth to spend 30 minutes to learn these 3 words )
Step1. define a async function like this:
async query_1() {
new Promise(resolve => {
db = ...
db.serialize( () => {
db.get('select .. from ... where id = 1', [], (error, row) => {
// here is the KEY: put the result into resolve
// this equals to the "return" statement in non-sync method.
resolve(row)
}
})
db.close()
})
}
and also implement your query_2 function like this:
async query_2() {
let query_1_result = await this.query_1()
db = ...
db.serialize( () => {
db.get('select .. from ... where dependency_id = ' + query_1_result, [], (error, row) => {
// other code here...
}
})
db.close()
}
refer to my answer: https://stackoverflow.com/a/67881159/445908
How about using 2 function to do these ?
function db_query1(your_param,...., callback){
  // database operation
db.run( sql , [param,...] , function(err,rows){
if(err) // return
else{
// get rows with callback
callback(null, rows);
}
});
}
function db_query2(your_param,...., callback){
  // database operation
db.run( sql , [param,...] , function(err,rows){
if(err) // return
else{
// get rows with callback
callback(null, rows);
}
});
}
And call these function:
db_query1(....,function(err,result1){
if(err) ...// return
// do the things with result1
// And then call query2
db_query2(....,function(err,result2){
if(err) ...// return
// do the things with result1
});
});
Hope this will help :)
You can use Promises.all, an array and the second callback for node sqlite3 db.each() that is executed when all rows have been fetched. Node Sqlite3 db.each usage to simplify the nested query and
I cannot really get the meaning of the variables you are using thus I assume that each row in Info1DB has a one-to-many relationship with DoorDB on the field doorId.
async function getInfo (callback) {
sql = "select * from Info1DB;";
numVersions = 0;
countVersions = 0;
info1JsonObj = [];
db.serialize(function() {
db.each(sql, [], (err, info1Row) => {
sql = "select * from DoorDB where ObjectID=?;";
info1Row.doors = [];
doorId = ...
db.each(sql, [doorId], (err, doorRow) => {
info1Row.doors.push(new Promise((resolve, reject) => {
if (err) {
reject(err);
} else {
resolve(doorRow);
}
}));
}, (err, num) => {
Promise.all(info1Row.doors)
.then((doors) => {
info1Row.doors = doors;
info1JsonObj.push(info1Row);
countVersions++;
if (countVersions == numVersions) {
callback(null, info1JsonObj);
}
}).catch((err) => {
callback(err, null);
});
});
}, (err, versions) => {
numVersions = versions;
});
});
}

Resources