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.
Related
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],
]
}, []);
})();
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.
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);
}
I have a cloud function that is triggered on a document write. The cloud function needs to check multiple documents based on the trigger and execute if/else statements.
I've created a function that accesses all documents with a Promise.all, but this errors when trying to access all the document information if not yet available.
export function onTriggered(change, context) {
const userPrevData = change.before.data();
const userNewData = change.after.data();
const promises = [];
// Get the uid
const uid = context.params.userId;
// User DocRef
const userDoc = firestoreInstance.collection('users').doc(uid).get();
// User Session DocRef
const userSessionDoc = firestoreInstance.collection('sessions').doc(uid).get();
// Solution DocRef
const solutionDoc = firestoreInstance.collection('solution').doc('solutionId').get();
promises.push(userDoc, userSessionDoc, solutionDoc);
return Promise.all(promises).then((snapshots) => {
// Use Promise.all with snapshot.docs.map to combine+return Promise context
return Promise.all(snapshots.map((doc) => {
// At this point, each document and their Ids print to the console.
console.log('docId:::', doc.id);
console.log('docData:::', doc.data());
const solutionDocData = getSolutionDocData(doc);
// This will print as 'undefined' until the correct document data is processed
console.log('solutionDocData:::', solutionDocData);
// This will print as 'undefined' until the correct document data is processed
const clientSeed = doc.get('clientSeed');
// Check to see if the Users' Guess is the same as the solution
if (userNewData.guess.color === solutionDocData.guess.color && userNewData.guess.number === userNewData.guess.number) {
console.log('User solution is correct');
}
}));
})
}
function getSolutionDocData(doc) {
if (doc.id === 'solutionId') { return doc.data(); }
}
I expect 'User solution is correct' if the condition is satisfied. But, I get an error because data is undefined.
The solution was to move most of the logic a .then()
return Promise.all(promises).then((snapshots) => {
// Use Promise.all with snapshot.docs.map to combine+return Promise context
return Promise.all(snapshots.map((doc) => {
// At this point, each document and their Ids print to the console.
console.log('docId:::', doc.id);
console.log('docData:::', doc.data());
return doc.data();
})).then(data => {
console.log('data:::', data);
let userDocDetails = {};
let userSessionDocDetails = {};
let solutionDocDetails = {};
data.forEach(document => {
if (document.uid === uid) { userDocDetails = document }
if (document.serverSeedEncrypted) { userSessionDocDetails = document }
if (document.solutionId) { solutionDocDetails = document }
});
});
})
I am unsure if the data will always be returned in the order of the original promise array, so I used a forEach statement to identify unique properties and assign them accordingly.
I am trying to get each property of my games within chained promises (Each of the property is coming from a different async calls).
Logic of my algorithm:
Check the Network and Get the smart contract address
Register the contract containing the addresses of all the Games
Get the number of Games
For each game, perform one aSync call
per property
Print all the games and details (here I am not able
to get the updated object)
Code:
var games = [];
window.addEventListener('load', function() {
// Check the Network and assign the smart contract address
web3.eth.net.getId()
.then(function(networkId) {
let contractAddressRegistry;
if (networkId == 1) {
contractAddressRegistry = "0xQWERTYUIOPQWERTYUIOPQWERTY"
} else {
contractAddressRegistry = "0x12345678901234567890123456"
}
return contractAddressRegistry;
})
.then(function(contractAddressRegistry) {
let contractRegistry = new web3.eth.Contract(contractAbiRegistry, contractAddressRegistry);
contractRegistry.methods.numberOfGames().call()
.then(function(numberOfGames) {
for (let i = 0; i < numberOfGames; i++) {
let game = {};
game.propertyA = aSyncCallGetPropertyA(i); // Promise
game.propertyB = aSyncCallGetPropertyB(i); // Promise
game.propertyC = aSyncCallGetPropertyC(i); // Promise
}
games.push(game);
})
})
.then(function() {
console.log(games) // Empty
})
})
I tried used Promises.all() but I am not able to sync it properly as some async calls are within a then().
How can I make sure to get the object Games filled with all its properties?
You should use Promise.all like this. Basically, you need to wrap all three aSyncCallGetProperty async calls in Promise.all for waiting until they really finish then assign the results to object game.
whatever
.then(function(contractAddressRegistry) {
let contractRegistry = new web3.eth.Contract(contractAbiRegistry, contractAddressRegistry);
return contractRegistry.methods.numberOfGames().call();
})
.then(function(numberOfGames) {
return Promise.all(numberOfGames.map(() => {
return Promise.all([
aSyncCallGetPropertyA(),
aSyncCallGetPropertyB(),
aSyncCallGetPropertyC()
]).then(results => {
let game = {};
game.propertyA = results[0];
game.propertyB = results[1];
game.propertyC = results[2];
return game;
});
}));
})
.then(function(games) {
console.log(JSON.stringify(games));
})
#Lewis' code seems right but I can not make sure what numberOfGames is. Assuming that it's an integer as used in your question (not an array as treated in the other answer) here is a further rephrased version without nested .then()s.
window.addEventListener('load', function() {
web3.eth.net.getId()
.then(networkId => networkId === 1 ? "0xQWERTYUIOPQWERTYUIOPQWERTY"
: "0x12345678901234567890123456")
.then(contractAddressRegistry => new web3.eth.Contract(contractAbiRegistry, contractAddressRegistry).methods.numberOfGames().call())
.then(numberOfGames => Promise.all(Array(numberOfGames).fill()
.map(_ => Promise.all([aSyncCallGetPropertyA(),
aSyncCallGetPropertyB(),
aSyncCallGetPropertyC()]))))
.then(function(games){
games = games.map(game => ({propertyA: game[0],
propertyB: game[1],
propertyC: game[2]}));
doSomethingWith(games);
});
});