Firestore join foreach map - get data from two collections - javascript

Hey Guys i need your help one more :(
I dont know how I make a Join or something else in Firebase
I have two collections:
collection("guests") has a datafield "Eventid" and a datafield "Userid"
in the first step i select all guests with a specific Userid. So i get all Eventids in a foreach loop (or as an Array)! In the second Collection
collection('events') I select all Information and write it in a JSON File. This works too after a couple of time and this is my problem!
I run this in a funktion and the function return before the Events were loaded.
I dont know how i use it i tried it with await and async or split it in two functions.
Maybye there is another way to realize it.
db.collection("guests").where("userid", "==", id).get().then(function(querySnapshot) {
querySnapshot.forEach(function (doc) {
db.collection('events').doc(doc.data().eventid).get().then(function(doc) {
if (doc.exists) {
console.log(doc.id, " => ", doc.data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
});
});

i got it!
exports.getEvent = functions.https.onRequest(async (req, res) => {
console.log('==NACHEINANDER STARTEN==');
const id = req.query.uid;
var arr = [];
var json = [];
let query = db.collection("guests").where("userid", "==", id);
await query.get().then(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
arr.push(documentSnapshot.data());
});
});
//console.log(arr);
await processArray(arr)
async function delayedLog(item) {
await db.collection('events').doc(item.eventid).get().then(function(doc) {
console.log(doc.data());
json.push(doc.data());
})
}
async function processArray(array) {
const promises = array.map(delayedLog);
// wait until all promises are resolved
await Promise.all(promises);
console.log('Done!');
}
console.log("hello");
res.status(200).send(JSON.stringify(json)); //, ...userData
});

Related

JavaScript - Async/Await - How to Return result from nested functions?

I'm new to Node.js and having difficulty working with async/await model. The issue I'm having is with nested function calls returning data in an async/await method, I always get 'undefined'. My functions structure is like this:
const findCustomerOrder = async (id) => {
const orders = await Order.find({custoemrId:id})
.then(async (order) => {
if(order)
{
const products = await findProductsByOrderId(order._id)
.then(async (result) => {
for(const r in result){
console.log("Product ID: ", result._id);
}
});
}
else
console.log("order not found");
});
}
const findProductsByOrderId = async (id) => {
try{
const products = await Products.find({orderId:id}, (error, data) => {
if(error) console.log(error);
else
return data;
});
}
catch(err){
return 'error!!';
}
}
I understand that if the top-level call is async/await then all the nested calls should be awaited as well that I've tried to do.
What am I doing wrong here?
Get rid of all the then, and also of the callback in the DB query. Use only await. It will make the whole code a lot easier to reason about and it should also solve your issue as part of the restructuring.
In the example below I also added a loop over the orders, because you are fetching all orders, as array, but then your code was behaving as if it got only one. I also fixed the products loop.
However, the way you fetch products doesn't seem to be right anyway, see my comment below. I didn't fix it because I don't know how your database structure looks, so I don't know what the right way would be.
async function findCustomerOrder (id) {
const orders = await Order.find({ customerId: id })
for (const order of orders) {
console.log(`Order ID: ${order._id}`)
const products = await findProductsByOrderId(order._id)
for (const product of products) {
console.log(`Product ID: ${product._id}`);
}
}
}
async function findProductsByOrderId (id) {
// Note: I don't think this is right. It seems to find all
// products with the given ID - which will always be one -
// and that ID would be the _product_ ID and not the order ID.
return await Products.find({ _id: id })
}
Preemptive comment reply: The return await is intentional, and this is why.
You should remove the then and also the callback and you can do something like below
const findCustomerOrder = async(id) => {
try {
const orders = await Order.find({
custoemrId: id
});
if (orders) {
let promiseArray = [];
orders.forEach(order => {
promiseArray.push(findProductsByOrderId(order._id));
});
let results = await Promise.all(promiseArray);
return results;
}
return "order not found";
} catch (err) {
throw err;
}
}
const findProductsByOrderId = async(id) => {
try {
const products = await Products.find({
_id: id
});
return products;
} catch (err) {
throw err;
}
}

Promise trouble! How can I write this in such a way that it waits for the end?

Trying to write this in a way that it will wait for the db operations to complete. Possible?
function addFriendsToSession(session, sessionId) {
const friends = [];
const incoming = session.users.forEach(async user => {
console.log('user', user);
await db
.collection('users/' + user + '/settings')
.doc(user)
.get()
.then(doc => {
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
});
friends.push(incoming);
return friends;
}
Use Promise.all.
Promise.all accepts an array of promises, and resolves once each promise has resolved. You can map your operation using map. E.g.,
const promises = session.users.map(user => {
console.log('user', user);
return db
.collection('users/' + user + '/settings')
.doc(user)
.get()
.then(doc => {
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
});
const friends = await Promise.all(promises)
return friends;
There are a number of issues here.
In db.then(), return is used, but this value is never returned from the function that encloses it (which is async user => {...})
const incoming is assigned the result of session.users.forEach, but Array.forEach() never has a return value (you may be thinking of Array.map()?)
Even if you solved the first two problems, incoming would still be an array of Promises
Additional suggestions:
Don't mix async/await with .then
Putting it all together:
const incoming = session.users.map(async user => {
console.log('user', user);
const doc = await db
.collection('users/' + user + '/settings')
.doc(user)
.get();
//we assigned `doc` using await instead of using .then
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
//incoming is not an array of Promises
const incomingFriends = await Promise.all(incoming); //takes an array of Promises and returns an array of the resolved values
//incomingFriends is now an array of friends
//next line would make the last element an array
//friends.push(incoming);
//you probably want to push every element of the array
friends.push(...incomingFriends);

Await sql and push to array before executing the result

I'm using mysql npm package to get data from the database.
My goal is to add every list(id) from lists Table and push it into lists array.
I'm getting the correct data from the database but when I result the query lists array is empty.
I think that I have to add async and await to the code to make it work. Tried in several places but I didn't make it work. Any idea what I'm doing wrong?
// GET - get all grocery_lists
Grocery_list.getAll = (result) => {
let lists = []; // <--- List Array
sql.query("SELECT id FROM lists", (err, res) => { // <--- Get all id from 'lists' table
if (err) {
console.log(err);
}
res.forEach(list => { // <--- Loop thru all lists
sql.query(`
SELECT items.id, items.name, items_x_lists.purchased
FROM items_x_lists
INNER JOIN items ON items_x_lists.itemId = items.id
WHERE items_x_lists.listId = ${list.id};
`, (err, res) => { // <--- Get all items for ${list.id} from 'items' table
if (err) {
console.log(err);
}
const list = {};
list.id = res.id;
console.log(list); // <--- { id: 1 } ... { id: 2 }
lists.push(list);
});
});
result(null, lists); // <--- returning empty array instead of [{ id: 1 }, { id: 2 }]
});
};
I think you can do it simply like this (not the exact code) but I think you've got the idea:
// GET - get all grocery_lists
Grocery_list.getAll = (result) => {
getData().then(data => {
// call result(data);
}).catch(err => {
// handle error for no data or any other errors
})
}
const getData = async () => {
try {
var res = await sql.query("SELECT id FROM lists");
// Handle error if no data found
if (!res) { Promise.reject("No data found"); }
} catch (error) {
return Promise.reject(error);
}
const listIds = res.map(id => id); // array of ids
try {
var data = await sql.query(`
SELECT items.id, items.name, items_x_lists.purchased
FROM items_x_lists
INNER JOIN items ON items_x_lists.itemId = items.id
WHERE items_x_lists.listId in ${listIds};`);
// Handle error if no data found
if (!data) { return Promise.reject("No data found") }
} catch (error) {
return Promise.reject(error);
}
return Promise.resolve(data)
}
this mighht be the solution
Grocery_list.getAll = async (result) => { return sql.query("SELECT id FROM lists", (err, res) => { // <--- Get all id from 'lists' table if (err) { console.log(err); } }); };
I’m outside, so I can only edit with my mobile phone. If the layout is strange, I apologize for it.
First of all, if it is me, I will adjust a few places
1.Create an async function and use Promise.all technology
async function queryAll(querys){
return await Promise.all(querys);
}
2.Create a Promise function to execute each sql
const queryPromise = id => {
return new Promise((resolve, reject) => {
sql.query(
'SELECT items.id, items.name, items_x_lists.purchased FROM items_x_lists INNER JOIN items ON items_x_lists.itemId = items.id WHERE items_x_lists.listId = ? ;',
id,
(err, rows, fields) => {
console.log(rows);
if (err) reject(err);
else resolve(rows);
}
);
});
};
3.Adjust the internal logic of the getAll event
const querys = []
res.forEach(list => {
querys.push(queryPromise(list.id));
});
const querysResults = await queryAll(querys);// remember to use async for function
querysResults.forEach(querysResult => {
lists.push({id:querysResult['id']});
});
Because there is no computer on hand, there may be some situations that need to be judged, but roughly this will work normally.
Hope this helps you :)

Function code isn't executed in the right order, async-wait is implemented wrongly

What I'm trying to do in my endpoint, is:
Make an API call, which returns a JSON
for each item: search in our database for it
If it's found, skip it.
If it's not found, push it in an array "Response"
This is my code:
app.get("/test", (req,res) => {
spotifyApi.getUserPlaylists({ limit: 50 })
.then(function(data) {
let finalres = [];
const tbp = data.body.items;
// res.send('ok stop loading');
tbp.forEach(element => locateit(element,finalres));
console.log('This is the length of finalres, which should be 1:', finalres.length);
finalres.forEach(item =>{console.log(item)});
function locateit(item, finalres){
const thisplaylistid = item.id;
collection.find({ "id" : thisplaylistid }).toArray((error, result) => {
if(error) {
return res.status(500).send(error);
}
if(result.length == 0) { // if we don't find this playlist in our DB
console.log('This playlist is not in our database: ');
console.log(thisplaylistid);
finalres.push(thisplaylistid);
}
else{ //if it's already in our DB
console.log('This item is in our database.'); //This should be printed first, six times.
}
});
};
});
});
The content of data.body.items is 7 items, where only the first 6 of them are in our DB. This means, that the last item, should be pushed in finalres.
Therefore, the expected console outcome should be:
This item is in our database.
This item is in our database.
This item is in our database.
This item is in our database.
This item is in our database.
This playlist is not in our database:
3uDLmuYPeRUxXouxuTsWOe
This is the length of finalres, which should be 1: 1
3uDLmuYPeRUxXouxuTsWOe
But instead, I get this:
This is the length of finalres, which should be 1: 0
This should be displayed first, six times.
This should be displayed first, six times.
This should be displayed first, six times.
This should be displayed first, six times.
This should be displayed first, six times.
This should be displayed first, six times.
This playlist is not in our database:
3uDLmuYPeRUxXouxuTsWOe
It is obviously not executed in the right order. I tried to use async-wait, but I'm struggling to understand where/how it should be implemented. Any help?
This is the part where I tried it, but I get the same console outcome as before:
async function locateit(item, finalres){
const thisplaylistid = item.id;
await collection.find({ "id" : thisplaylistid }).toArray((error, result) => {
...
Update
After reading more about async-wait and promises, I tried to do it this way, but I'm still getting the same output.
app.get("/test", (req,res) => {
spotifyApi.getUserPlaylists({ limit: 50 })
.then(function(data) {
let finalres = [];
const tbp = data.body.items;
// res.send('ok stop loading');
for (const playlist of tbp) {
async function doWork() {
const found = await indb(playlist.id); //returns t/f if found or not found
if (!found){
finalres.push(playlist);
}
}
doWork();
}
console.log('This is the length of finalres and it should be 1: ',finalres.length);
})
});
and the indb function looks like that:
function indb(thisplaylistid){
return new Promise((resolve, reject) =>{
console.log('Searching in our DB...');
collection.find({ "id" : thisplaylistid }).toArray((error, result) => {
if(result.length == 0) { // if we don't find this playlist in our DB
console.log('This playlist is not in our database: ');
console.log(thisplaylistid);
resolve(false); //returns the id
}
else{ //if it's already in our DB
console.log('This item is in our database.'); //This should be printed first, six times.
resolve(true);
}
});
})
}
The problem here is that forEach resolves always resolves as void, no matter if you have async promises running within.
So, your code will return before executing the statements within the forEach
The correct would be wait for all promises to resolve using #Promise.all
Try this instead:
Updated
Using promise as suggested by Bergi instead of callback ( preferable )
app.get("/test", (req, res) => {
spotifyApi.getUserPlaylists({ limit: 50 })
.then((data) => {
// :refac: more meaningful variable names
const playlists = data.body.items
return Promise.all(
playlists.map(
// :refac: destructuring to get only the id, other ain't necessary
async({ id }) =>
collection.find({ id }).toArray()
)
)
.then(playlistsById =>
// :refac: no error occurred fetching playlists
const nonEmptyPlaylists = playlistsById.filter(playlistById => playlistById.length !== 0)
res.status(200).send(nonEmptyPlaylists)
)
.catch(error => {
// :refac: some error occurred at searching some playlist
console.log('error', error)
// :refac: if you might expect that is going to throw an error here, the code shouldn't be 500
return res.status(400).send(error)
})
})
})
As others mentioned, your usage of async/await is wrong. I believe this should work and do what you want, and as a bonus its shorter and easier to read. mastering async and await will simplify your life and save you from callback/promise hell, i highly recommend it
app.get("/test", async (req, res) => {
try {
const data = await spotifyApi.getUserPlaylists({ limit: 50 });
const tbp = data.body.items;
const results = [];
for(let item of tbp) {
const found = await indb(item.id);
if(!found){
results.push(item);
}
}
return res.status(200).send(results);
}
catch(err) {
return res.status(400).send(err);
}
});

Reading collection from Firestore with Cloud Function

I'm trying to get the document in my collection "Cities" in Firestore using cloud function, the collection is the same as the tutorial.
'use strict';
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
var db = admin.firestore();
var test ;
exports.helloWorld = (req, res) => {
var citiesRef = db.collection('cities/');
var allCities = citiesRef.get()
.then(snapshot => {
snapshot.forEach(doc => {
//console.log(doc.id, '=>', doc.data());
test = doc.data();
res.status(200).send("Data");
});
})
.catch(err => {
console.log('Error getting documents', err);
});
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(test);
};
The problem is that snapshot is empty so it doesn't return my response message. What is the correct way of reading all the data in the collection for cloud function?
You must send the response only when data fetch from the database.
exports.helloWorld = (req, res) => {
var citiesRef = db.collection('cities');
var allCities = citiesRef.get()
.then(snapshot => {
if (snapshot.empty) {
return res.status(404).send("No data to found");
}
// create data array
const snapshotData = [];
snapshot.forEach(doc => {
test = doc.data();
// put data into the array
snapshotData.push(test);
});
// send response with all data
res.status(200).send(snapshotData);
})
.catch(err => {
console.log('Error getting documents', err);
res.status(500).send(err);
});
};
It seems to me that none of your calls to res.send() are in a good place. It must only be called once for the entire function. You have one inside a loop (which would be bad if the query returned multiple documents), and another one sending a response before anything happens with the query, which is asynchronous. Calling then does not pause the program - it just returns immediately with a new promise. (You will definitely have to understand how JavaScript promises work in order to make effective use of Cloud Functions, otherwise nothing will work as you expect.)
Try something more like this, where you send only one response after the query is complete, and also send and error response in that case.
exports.helloWorld = (req, res) => {
var citiesRef = db.collection('cities/');
var allCities = citiesRef.get()
.then(snapshot => {
snapshot.forEach(doc => {
//console.log(doc.id, '=>', doc.data());
test = doc.data();
});
res.status(200).send(test);
})
.catch(err => {
console.log('Error getting documents', err);
res.status(500).send("Some error");
});
});

Categories