Filter and map Promise with async - javascript

I got promise object, that returned by sequelize.findAndCountAll(), and I wanna filter the object before mapping them.
Here's my code:
async function getCountTask(id){
return await Tasks.count({
where: {
studentID: id,
status: 'Done',
grade: ['A', 'B']
}
});
}
let totalStudent = [];
await Promise.all(
listStudent.filter(async (f) => {
const count = await getCountTask(f.id);
if(count <= 3){
return false;
}
return true;
}).map(async (e) => {
let obj = {};
obj.id = e.id;
obj.age = e.age;
obj.status = 'Great';
totalStudent.push(obj);
})
)
My expectation listStudent contain 5 data, but after filter, it will only contain 3 data because 2 another didn't pass the condition. So for ther final result the totalStudent containing 3 data.
But what I got from the code above is, the totalStudent have data exactly like listStudent.
It's because it process the map first and then the filter, so the map process data that didn't filter yet.
How do I can make it become filter first and then map the data.
Thanks in advance.

Filter / map etc, don't support async in the way your using them.
eg. filter expects either a true or false, your returning promise async functions is always a promise.
await Promise.all(
listStudent.filter(async (f) => { <<<<---- This makes filter always return true
const count = await getCountTask(f.id);
Looking at your code, a simple solution is just remove the filter, and just use map..
eg..
await Promise.all(
listStudent.map(async (f) => {
const count = await getCountTask(f.id);
if (count <= 3) return; //don't want this one.
let obj = {};
obj.id = f.id;
obj.age = f.age;
obj.status = 'Great';
totalStudent.push(obj);
})
)
Seen as Promise.all returns an array, you can also avoid using push on totalStudent,.
eg.
totalStudent = await Promise.all(
listStudent.map(async (f) => {
const count = await getCountTask(f.id);
if (count <= 3) return; //don't want this one.
let obj = {};
obj.id = f.id;
obj.age = f.age;
obj.status = 'Great';
return obj;
})
)
The advantage of the above is that the return order is also maintained.

Related

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

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],
]
}, []);
})();

Store and update a stored Object

Im trying to store an object in AsyncStorage then retrieve it and merge it with the current one and save it again, thus keeping and updated object at all time. Im really struggling to understand the way you are supposed to store and manipulate this, any suggestions ? this is done in component will mount when the component and app loads ps I only manage to make a new array element with all the object
retrieve method
_retrieveObj = async () => {
try {
const value = await AsyncStorage.getItem('OBJECT');
if (value !== null) {
return JSON.parse(value);
}
return [];
} catch (error) {
// Error retrieving data
}
};
store method
_storeObj = async (obj) => {
let numberArray = [];
try {
let storedNumbers = await AsyncStorage.getItem('OBJECT');
if (storedNumbers !== null) {
numberArray = JSON.parse(storedNumbers);
}
numberArray.push(obj)
await AsyncStorage.setItem('OBJECT', JSON.stringify(numberArray));
} catch (error) {
console.log(error)
}
};
call within Component
_UpdateAndSave = async (objToDisplay) => {
const storedObj = await this._retrieveObj();
if (storedObj !== objToDisplay) {
const obj = this._merge(storedObj ,objToDisplay);
const objToSave = JSON.stringify(obj);
this._storeObj(objToSave);
}
method to merge objects (found online)
_merge = (a, b) => {
let c = {};
for(let idx in a) {
c[idx] = a[idx];
}
for(let idx in b) {
c[idx] = b[idx];
}
return c;
};
Thanks
The reason you are getting an array is because you are saving an array of objectsin your _storeObj function. It looks like you already merge the existing and new objects into one, so you simply have to save the result of the merge. You shouldn't need your _storeObj function.
_UpdateAndSave = async (objToDisplay) => {
const storedObj = await this._retrieveObj();
if (storedObj !== objToDisplay) {
const obj = this._merge(storedObj ,objToDisplay);
const objToSave = JSON.stringify(obj);
await AsyncStorage.setItem('OBJECT', objToSave);
}

Javascript not parsing array passed over through function

I have an array which is passed through a function using javascript, I can't see anything wrong with the code but it doesn't pass over the first array correctly so it can be parsed.
The idea is the first array is 56 items, it then calls the parseData function which is supposed to split this array into chunks of 7.
Here is the two functions:
static async validateRowValues() {
let data = [];
await cy.get('tr > td > div.dlCell')
.each(function (row) {
let d = row.get(0).innerText;
data.push(d);
});
console.log(data);
let response = await this.parseData(data);
console.log({response});
}
static async parseData(tData) {
console.log(tData);
let array = [];
let coll_array = [];
debugger;
await tData.forEach(async (v, index) => {
await array.push(v);
if (index % 6 === 0 && index !== 0) {
await coll_array.push(array);
array = [];
}
});
return coll_array;
}
The first console.log within parseData does return the 56 items, however by the time it reaches tData.forEach it has completely lost its data, and the parsing returns an empty array when it returns coll_array.
If anyone has any ideas?
As of now I will take it you getting your data fine in array.
ex. arr = [1,2,3,.....58]
Use below code to split in chunks of 7
arr = arr.reduce((acc,data,index)=>{
if(index==0 || index%7==0) acc.push([])
acc[acc.length-1].push(data)
return acc
},[])
The above code will return
arr = [ [1,..,7], [8,...14], ....]
We have resolved this.
It turns out everything in Cypress is a promise so the first function needed to have a .then
static async validateRowValues() {
let data = [];
await cy.get('tr > td > div.dlCell')
.each(function (row) {
let d = row.get(0).innerText;
data.push(d);
}).then(() => {
this.parseData(data);
});
}

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.

Promise/async-await with mongoose, returning empty array

The console in the end returns empty array.
The console runs before ids.map function finishes
var ids = [];
var allLync = []
var user = await User.findOne(args.user)
ids.push(user._id)
user.following.map(x => {
ids.push(x)
})
ids.map(async x => {
var lync = await Lync.find({ "author": x })
lync.map(u => {
allLync.push[u]
})
})
console.log(allLync)
What am I doing wrong?
The .map code isn't awaited, so the console.log happens before the mapping happens.
If you want to wait for a map - you can use Promise.all with await:
var ids = [];
var allLync = []
var user = await User.findOne(args.user)
ids.push(user._id)
user.following.map(x => {
ids.push(x)
})
// note the await
await Promise.all(ids.map(async x => {
var lync = await Lync.find({ "author": x })
lync.map(u => {
allLync.push(u); // you had a typo there
})
}));
console.log(allLync)
Note though since you're using .map you can shorten the code significantly:
const user = await User.findOne(args.user)
const ids = users.following.concat(user._id);
const allLync = await Promise.all(ids.map(id => Lync.find({"author": x })));
console.log(allLync);
Promise.map() is now an option that would be a tiny bit more succinct option, if you don't mind using bluebird.
It could look something like:
const user = await User.findOne(args.user);
const ids = users.following.concat(user._id);
const allLync = await Promise.map(ids, (id => Lync.find({"author": x })));
console.log(allLync);
http://bluebirdjs.com/docs/api/promise.map.html. I have really enjoyed using it.

Categories

Resources