How to add MySQL query results from a loop in Nodejs? - javascript

Essentially, I have an object with string keys and values (ex. {"michigan":"minnesota"}). I'm trying to loop through all of these key value pairs and make a query from my database, and add the result to a list, which will then be what is returned to the front end.
var return_list = []
Object.keys(obj).forEach(function(key){
const state1 = key;
const state2 = obj[key];
const sql_select = 'SELECT column1,column2 from database WHERE state = ? OR state=?';
db.query(sql_select,[state1,state2], (err,result) => {
return_list.push(result);
});
})
This is what I have in simplest terms, and would like to send return_list back to the front end. The problem I'm running into is I can console.log the result within db.query call, but I can't push the result to the list or call it anywhere outside of the query. I'm fairly new to both front end and back end development, so any possible ideas would definitely be helpful!

The problem is that the forEach returns void.
So you can't wait for the asynchronous code to run before you return it.
When we're dealing with an array of promises such as db queries ( like in your case ) or API calls, we should wait for every one of them to be executed.
That's when we use the Promise.all
Try doing it like this:
const queryResults = await Promise.all(
Object.keys(obj).map(async (key) => {
const state1 = key;
const state2 = obj[key];
const sql_select = 'SELECT column1,column2 from database WHERE state = ? OR state=?';
return new Promise((resolve, reject) =>
db.query(sql_select,[state1,state2], (err, result) => {
if (err)
return reject(err)
else
return resolve(result)
})
)
})
)
console.log('queryResults', queryResults)
// now you give this queryResults back to your FE
Small tips for your fresh start:
never use var, try always use const or if needed, let.
try always use arrow functions ( () => {...} ) instead of regular functions ( function () {...} ), It's hard to predict which scope this last one is using

The issue is because the database transaction is not instant, so you need to use either promises or async-await.
Using async await would be something like this (untested),
async function get_return_list () {
var return_list = []
Object.keys(obj).forEach(function(key){
const state1 = key;
const state2 = obj[key];
const sql_select = 'SELECT column1,column2 from database WHERE state = ? OR state=?';
await db.query(sql_select,[state1,state2], (err,result) => {
return_list.push(result);
});
})
return return_list
}
see for more detail: https://eloquentjavascript.net/11_async.html

First, make sure you are working with mysql2 from npm. Which provides async method of mysql.
Second, Note that when you query SELECT, you don't get the "real" result in first. Suppose you get result, then, the "real" results are held in result[0].
(async () => {
const promiseArr = [];
for (const key of Object.keys(yourOBJ)) {
const state1 = key;
const state2 = yourOBJ[key];
const sql_select = 'SELECT column1,column2 from database WHERE state = ? OR state=?';
promiseArr.push(db.query(sql_select, [state1, state2]));
}
let results;
try {
results = await Promise.all(promiseArr);
} catch (e) {
throw '...';
}
const return_list = results.reduce((finalArray, item) => {
finalArray = [
...finalArray,
...item[0],
]
}, []);
})();

Related

Block code until first forEach loop finishes to use result in another forEach loop in Node

There are similar questions like this one here in stackoverflow, but I can't find one that resolves this in particular. I have two forEach loops in Node and one (this one: s.forEach((su) => { cw1.push(su.courseWorkId); }); ) uses the results of the previous one to do its iteration, but it fires asynchronously before the first one finishes so it always tells me that "const s" (the result of the first forEach loop) is empty. Everything else in the code works fine. Any help would be appreciated.
const fs = require('fs');
const path = require('path');
const {google} = require('googleapis');
const keys = require("./file.json");
const sc = require('./jwt.js');
const scopes = sc.scopes;
const j2 = new google.auth.JWT(keys.client_email,null,keys.private_key,scopes,'me#email.com');
const classroom = google.classroom({version: 'v1', auth:j2});
const cl = classroom.courses;
let cl1 = ['34809075556', '34800434710'];
let cw1 = [];
function getwi(){
cl1.forEach((ids) => {
cl.courseWork.list({courseId:ids}, (e,r) => {
const s = r.data.courseWork;
s.forEach((su) => { cw1.push(su.courseWorkId); });
});
});
}
getwi();
Modifying a global variable asynchronously is generally a bad idea. Instead wrap the asynchronous task into a promise, then resolve that promise to the value you need.
const getCourseWork = courseId => new Promise((resolve, reject) => cl.courseWork.list({ courseId, }, (err, result) => err ? reject(err) : resolve(result)));
Then you can get the result as:
Promise.all( cl1.map(getCourseWork) )
.then(result => console.log("The result is:", result.flat().map(it => it.id)))
.catch(error => console.error("An error occured:", error));
That way, all the calls are done in parallel, and are thus faster as doing one after another.
Read on

Pushing elements into the array works only inside the loop

I got some data which I'm calling from API and I am using axios for that. When data is retrieved, I dump it inside of a function called "RefractorData()" just to organize it a bit, then I push it onto existing array. The problems is, my array gets populated inside forEach and I can console.log my data there, but once I exit the loop, my array is empty.
let matches: any = new Array();
const player = new Player();
data.forEach(
async (match: any) => {
try {
const result = await API.httpRequest(
`https://APILink.com/matches/${match.id}`,
false
);
if (!result) console.log("No match info");
const refractored = player.RefractorMatch(result.data);
matches.push({ match: refractored });
console.log(matches);
} catch (err) {
throw err;
}
}
);
console.log(matches);
Now the first console.log inside forEach is displaying data properly, second one after forEach shows empty array.
Managed to do it with Promise.all() and Array.prototype.map()
.
const player = new Player();
const matches = result.data;
const promises = matches.map(async (match: any) => {
const response: any = await API.httpRequest(
`https://API/matches/${match.id}`,
false
);
let data = response.data;
return {
data: player.RefractorMatch(data)
};
});
const response: any = await Promise.all(promises);
You must understand that async functions almost always run later, because they deppend on some external input like a http response, so, the second console.log is running before the first.
There a few ways to solve this. The ugliest but easiest to figure out is to create a external promise that you will resolve once all http requests are done.
let matches = [];
let promise = new Promise((resolve) => {
let complete = 0;
data.forEach((match: any) => {
API.httpRequest(...).then((result) => {
// Your logic here
matches.push(yourLogicResult);
complete++;
if (complete === data.length) {
resolve();
}
}
}
};
console.log(matches); // still logs empty array
promise.then(() => console.log(matches)); // now logs the right array
You can solve this using other methods, for example Promise.all().
One very helpful way to solve it is using RxJs Observables. See https://www.learnrxjs.io/
Hope I helped you!

Pass array in promise chain

I'm trying to send notifications for all guests who are invited to an event.
The below code works up to the console.log(guestPlayerIds); which returns undefined. What is the best way to pass guestPlayerIds?
exports.sendNewEventFcm = functions.database.ref('/fcm/{pushID}').onWrite(event => {
const eventID = event.params.pushID;
const dsnap = event.data.val();
// Exit when the data is deleted.
if (!event.data.exists()) {
return;
}
let guests = Object.keys(dsnap);
const promises = guests.map((data) => admin.database().ref('playerIds').child(data).once('value'));
return Promise.all(promises).then(data => {
let guestPlayerIds = [];
let returns = data.map(item => {
let itemVal = Object.keys(item.val())[0];
console.log(Object.keys(item.val())[0])
guestPlayerIds.push(itemVal);
});
}).then(guestPlayerIds => {
console.log(guestPlayerIds);
})
});
Using map is good, but you should return something inside the callback, and then also return the result of the overall map:
return data.map(item => {
let itemVal = Object.keys(item.val())[0];
console.log(Object.keys(item.val())[0])
return itemVal;
});
You actually don't need the array variable guestPlayerIds at that point. Only in the subsequent then you can use it like you already had it.

typescript promise return after loop

I am trying to return after all the calls to an external service are done but my code is just flying through the for loop and returning. I cannot do a promise.all here as I need some values obtained in the loop. Is there an easy way to do this in typescript/angular2?
var ret = [];
for (var l in users){
var userLicense = users[l].licenseNumber;
this.callExternalService(userLicense).subscribe(response=>{
ret.push(response+userLicense);
}
}
resolve(ret);
As you are already using observables, you can use a combination of forkJoin and map to achieve this:
var ret = [];
Observable.forkJoin(
users.map(
user => this.callExternalService(user.licenseNumber)
.map(response => response + user.licenseNumber)
)
).subscribe(values => {
ret = values;
resolve(ret);
})
You could try combineLatest:
return Observable.combineLatest(
...users.map(user => this.callExternalService(user.licenseNumber)
.map(response => response + user.licenseNumber)),
(...vals) => vals);
This will return an observable of arrays of results from the service calls, which you can then subscribe to, convert to a promise with toPromise if you prefer, etc.
if you need to do some aggregation in the loop and pass it as part of return, you can add it into a Promise.all as Promise.resolve(aggregatedData)
Something like that:
function getData(ii) {
return new Promise(res => {
res(ii);
});
}
function getUserData(users) {
let promiseArray = [];
let aggregatedData = 0;
users.forEach(user => {
aggregatedData += user.id;
promiseArray.push(getData(user.id));
});
promiseArray.push(Promise.resolve(aggregatedData));
return Promise.all(promiseArray);
}
getUserData([{id:1}, {id:2}, {id:3}]).then(x=>console.log(x))

Best es6 way to get name based results with Promise.all

By default the Promise.All([]) function returns a number based index array that contains the results of each promise.
var promises = [];
promises.push(myFuncAsync1()); //returns 1
promises.push(myFuncAsync1()); //returns 2
Promise.all(promises).then((results)=>{
//results = [0,1]
}
What is the best vanilla way to return a named index of results with Promise.all()?
I tried with a Map, but it returns results in an array this way:
[key1, value1, key2, value2]
UPDATE:
My questions seems unclear, here is why i don't like ordered based index:
it's crappy to maintain: if you add a promise in your code you may have to rewrite the whole results function because the index may have change.
it's awful to read: results[42] (can be fixed with jib's answer below)
Not really usable in a dynamic context:
var promises = [];
if(...)
promises.push(...);
else{
[...].forEach(... => {
if(...)
promises.push(...);
else
[...].forEach(... => {
promises.push(...);
});
});
}
Promise.all(promises).then((resultsArr)=>{
/*Here i am basically fucked without clear named results
that dont rely on promises' ordering in the array */
});
ES6 supports destructuring, so if you just want to name the results you can write:
var myFuncAsync1 = () => Promise.resolve(1);
var myFuncAsync2 = () => Promise.resolve(2);
Promise.all([myFuncAsync1(), myFuncAsync2()])
.then(([result1, result2]) => console.log(result1 +" and "+ result2)) //1 and 2
.catch(e => console.error(e));
Works in Firefox and Chrome now.
Is this the kind of thing?
var promises = [];
promises.push(myFuncAsync1().then(r => ({name : "func1", result : r})));
promises.push(myFuncAsync1().then(r => ({name : "func2", result : r})));
Promise.all(promises).then(results => {
var lookup = results.reduce((prev, curr) => {
prev[curr.name] = curr.result;
return prev;
}, {});
var firstResult = lookup["func1"];
var secondResult = lookup["func2"];
}
If you don't want to modify the format of result objects, here is a helper function that allows assigning a name to each entry to access it later.
const allNamed = (nameToPromise) => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult = {};
for (let i = 0; i < results.length; ++i) {
const name = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
Usage:
var lookup = await allNamed({
rootStatus: fetch('https://stackoverflow.com/').then(rs => rs.status),
badRouteStatus: fetch('https://stackoverflow.com/badRoute').then(rs => rs.status),
});
var firstResult = lookup.rootStatus; // = 200
var secondResult = lookup.badRouteStatus; // = 404
If you are using typescript you can even specify relationship between input keys and results using keyof construct:
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
export const allNamed = <
T extends Record<string, Promise<any>>,
TResolved extends {[P in keyof T]: ThenArg<T[P]>}
>(nameToPromise: T): Promise<TResolved> => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult: TResolved = <any>{};
for (let i = 0; i < results.length; ++i) {
const name: keyof T = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
A great solution for this is to use async await. Not exactly ES6 like you asked, but ES8! But since Babel supports it fully, here we go:
You can avoid using only the array index by using async/await as follows.
This async function allows you to literally halt your code inside of it by allowing you to use the await keyword inside of the function, placing it before a promise. As as an async function encounters await on a promise that hasn't yet been resolved, the function immediately returns a pending promise. This returned promise resolves as soon as the function actually finishes later on. The function will only resume when the previously awaited promise is resolved, during which it will resolve the entire await Promise statement to the return value of that Promise, allowing you to put it inside of a variable. This effectively allows you to halt your code without blocking the thread. It's a great way to handle asynchronous stuff in JavaScript in general, because it makes your code more chronological and therefore easier to reason about:
async function resolvePromiseObject(promiseObject) {
await Promise.all(Object.values(promiseObject));
const ret = {};
for ([key, value] of Object.entries(promiseObject)) {
// All these resolve instantly due to the previous await
ret[key] = await value;
};
return ret;
}
As with anything above ES5: Please make sure that Babel is configured correctly so that users on older browsers can run your code without issue. You can make async await work flawlessly on even IE11, as long as your babel configuration is right.
in regards to #kragovip's answer, the reason you want to avoid that is shown here:
https://medium.com/front-end-weekly/async-await-is-not-about-making-asynchronous-code-synchronous-ba5937a0c11e
"...it’s really easy to get used to await all of your network and I/O calls.
However, you should be careful when using it multiple times in a row as the await keyword stops execution of all the code after it. (Exactly as it would be in synchronous code)"
Bad Example (DONT FOLLOW)
async function processData() {
const data1 = await downloadFromService1();
const data2 = await downloadFromService2();
const data3 = await downloadFromService3();
...
}
"There is also absolutely no need to wait for the completion of first request as none of other requests depend on its result.
We would like to have requests sent in parallel and wait for all of them to finish simultaneously. This is where the power of asynchronous event-driven programming lies.
To fix this we can use Promise.all() method. We save Promises from async function calls to variables, combine them to an array and await them all at once."
Instead
async function processData() {
const promise1 = downloadFromService1();
const promise2 = downloadFromService2();
const promise3 = downloadFromService3();
const allResults = await Promise.all([promise1, promise2, promise3]);

Categories

Resources