Firestore query for loop with multiple values - javascript

I am attempting to retrieve a number of Firestore documents using data held in a string. The idea is that for each value in the array, i'd use Firestore query to retrieve the document matching that query and push it to another array. I am having a few issues achieving this. So far i've tried:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let queryRef = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
}
response.send(JSON.stringify(outputList));
});
I'm not entirely sure but i think one of the issues is that the for loop is being completed before the queries have a chance to finish.
P.s - this is being ran through Cloud Functions using Admin SDK.

Your queryRef is not actually a reference. It's a promise that resolves after your get/then/catch have finished. You need to use these promises to determine when they're all complete. The array will be populated only after they are all complete, and only then is it safe to send the response using that array.
Collect all the promises into an array, and use Promise.all() to get a new promise that resolves after they're all complete:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let promise = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
promises.push(promise);
}
Promise.all(promises).then(() => {
response.send(JSON.stringify(outputList));
}
.catch(err => {
response.status(500);
})
});
You might want to use these tutorials to better understand how to deal with promises in Cloud Functions:
https://firebase.google.com/docs/functions/video-series/

You need to look into promises, Never called a promise in a loop like this. First, you need to chunk your code which returns result from the DB(asynchronously) and uses Promise.all() to handle multiple promises.
utils.getData = async (item) => {
try {
const result = await db.collection("items").where('listedItems', 'array-contains', item).get();
return result;
} catch (err) {
throw err;
}
};
utils.getDataFromDB = async () => {
try {
const list = ["item1", "item2", "item3", "item4"];
const outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
const element = list[i];
promises.push(utils.getData(elem));
}
const result = await Promise.all(promises);
result.forEach((r) => {
if (r.empty) {
console.log('No matching documents.');
} else {
snapshot.forEach(doc => {
outputList.push(doc.data());
});
}
});
return outputList;
} catch (err) {
throw err;
}
}
module.exports = utils;

Here is my attempt at fully idiomatic solution. It needs no intermediate variables (no race conditions possible) and it separates concerns nicely.
function data_for_snapshot( snapshot ) {
if ( snapshot && !snapshot.empty )
return snapshot.map( doc => doc.data() );
return [];
}
function query_data( search ) {
return new Promise( (resolve, reject) => {
db
.collection("items")
.where('listedItems', 'array-contains', search)
.get()
.then( snapshot => resolve(snapshot) )
.catch( resolve( [] ) );
});
}
function get_data( items )
{
return new Promise( (resolve) => {
Promise
.all( items.map( item => query_data(item) ) )
.then( (snapshots) => {
resolve( snapshots.flatMap(
snapshot => data_for_snapshot(snapshot)
));
});
});
}
get_data( ["item1", "item2", "item3", "item4"] ).then( function(data) {
console.log( JSON.stringify(data) );
});
I used a simple mockup for my testing, as i don't have access to that particular database. But it should work.
function query_data( search ) {
return new Promise( (resolve, reject) => {
setTimeout( () => {
resolve([{
data: function() { return search.toUpperCase() },
empty: false
}])
});
});
}

Related

How to wait for the final result of a for loop with API calls?

This loop is going to run an arbitrary amount of times, and I want to get the result out of it after them all. Anything I try (promisifying, async/await, nesting functions, et al) seems to be a dead end. I don't get why I cannot just stick a .then on the API call, or on the function I made here. But I suspect the problem is more fundamental with my understanding, because I can't seem to even get just the "data" to return...same inability to wait on the API call. Wrapping it in a promise loses the "data" and pulling it with the "for loop" inside there doesn't work either. This is making me question my entire progress with JS/implementing other people's APIs.
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
//return data //doesnt return "data" from gotPeeps?
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}}}})}
gotPeeps()
EDIT
To the comments:
I'm trying to get the results after the for loop is complete.
The "loop" I was referring to is the "for of" over the data Object.
"Putting the code after the loop but inside the callback" does not work. I have a previous question in my week of failing to solve this:
How to solve race condition in Javascript?
Here is the whole thing, with some past versions commented out.:
const tournamentModel = require('../models/tournamentSchema')
require('dotenv').config()
const challonge = require('challonge');
module.exports = {
name: 'getmatches',
aliases: ['gm'],
cooldown: 0,
description: 'Get Challonge data into console.',
execute(message, args, cmd, client, Discord, profileData) {
let peep = ''
let peepsList = ''
const tournamentURL = 'TESTING_Just_Sign_Up_If_You_See_This850587786533011496'
const challongeClient = challonge.createClient({
apiKey: process.env.CHALLONGE_API_KEY,
})
const getPlayer = (playerXId) => {
return new Promise((resolve, reject) => {
challongeClient.participants.show({
id: tournamentURL,
participantId: playerXId,
callback: (err, data) => {
if (err) {
reject(err);
return;
}
peep = data.participant.misc
peepsList.push(peep)
console.log(peepsList)
console.log('RUNNING GET PLAYER', playerXId, playerIndexCount)
resolve(peepsList);
}
});
});
}
const peepsList = []
const matchList = []
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
include_participants: 1,
include_matches: 1,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}
}
}
/*// GET PLAYERS
getPlayer(value.match.player1Id)
.then(() => {
getPlayer(value.match.player2Id)
})
}
*/
}
)
}
gotPeeps()
}}
You can make this function return a promise and await the function (as long as your function is an async function)
const gotPeeps = () => {
return new Promise((resolve, reject) => {
const peepsList = []; // declare the empty array to e filled
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === "open") {
peepsList.push(value.match.player1Id, value.match.player2Id);
}
}
resolve(peepsList); // resolve the promise with the filled array
// TODO: handle reject
},
});
})
};
(async () => {
try {
const result = await gotPeeps();
} catch (error) {
// TODO: handle error
}
})();

Trying to use await with promise in my code here - how to?

I've tried but failed in grasping clearly how javascript promises and await work! I somehow managed to cobble together a function that performs what I need in my node.js micro service, but I'm not sure if I'm doing it the right (optimal) way. Also, I achieved what I wanted using promise without await, but also I haven't done any extensive testing of my code to see if it is indeed running exactly the way I think it is. Here is my code that I currently have and works, but I'm not sure if I'm missing using await for proper functioning:
const QryAllBooks = {
type: new GraphQLList(BookType),
args: {},
resolve(){
return new Promise((resolve, reject) => {
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err){
reject(err);
}
resolve(results);
const str = JSON.stringify(results);
const json = JSON.parse(str);
const promises = [];
for (let p = 0; p < results.length; p++){
const book_id = json[p].bookid;
const query = `mutation updateShipping
{updateShipping
(id: ${book_id}, input:{
status: "Shipped"
})
{ bookid
bookname }}`
promises.push(apolloFetch({ query }));
}
//I need an await function so that previous apolloFetch
//goes in sequence of bookid, one after the other
Promise.all( promises ).then(( result) => {
errorLogger(27, 'Error', result);
})
.catch(( e ) => {
errorLogger( 29, 'Error', e );
)};
});
});
}
};
module.exports = {
QryAllBooks,
BookType
};
Avoid the Promise constructor antipattern - you should not be doing anything after the call to resolve inside the promise executor. Put all that stuff in a then callback on the new Promise:
resolve() {
return new Promise((resolve, reject) => {
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err) reject(err);
else resolve(results);
});
}).then(results => {
const str = JSON.stringify(results);
const json = JSON.parse(str);
const promises = [];
for (let p = 0; p < results.length; p++){
const book_id = json[p].bookid;
const query = `mutation updateShipping {
updateShipping(id: ${book_id}, input:{
status: "Shipped"
}) { bookid
bookname }
}`;
promises.push(apolloFetch({ query }));
}
return Promise.all(promises);
}).then(result => {
errorLogger(27, 'Result', result);
return result;
}, err => {
errorLogger(29, 'Error', err);
throw err;
)};
}
You can now replace those then calls with await syntax. And also exchange the Promise.all for a sequential awaiting in the loop:
async resolve() {
try {
const results = await new Promise((resolve, reject) => {
// ^^^^^
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err) reject(err);
else resolve(results);
});
});
const promises = results.map(res => {
const book_id = res.bookid;
const query = `mutation updateShipping {
updateShipping(id: ${book_id}, input:{
status: "Shipped"
}) { bookid
bookname }
}`;
return apolloFetch({ query });
});
const result = await Promise.all(promises);
// ^^^^^
errorLogger(27, 'Result', result);
return result;
} catch(err) {
errorLogger(29, 'Error', err);
throw err;
}
}
async resolve() {
const results = await new Promise((resolve, reject) => {
// ^^^^^
let sql = singleLineString`
select distinct t.bookid,t.bookname,t.country
from books_tbl t
where t.ship_status = 'Not Shipped'
`;
pool.query(sql, (err, results) => {
if(err) reject(err);
else resolve(results);
});
});
const fetches = [];
for (let p = 0; p < results.length; p++){
const book_id = results[p].bookid;
const query = `mutation updateShipping {
updateShipping(id: ${book_id}, input:{
status: "Shipped"
}) { bookid
bookname }
}`;
fetches.push(await apolloFetch({ query }));
// ^^^^^
}
return fetches;
}

Run a function after a loop

I would like to run a function after the for loop done looping, but in my case the function after the for loop runs before the loop even finished.
Here's my code
let orderedItems = [];
for (let i = 0; i < orderCode.length; i++) {
menuModel.findOne({
_id: orderCode[i]
}, (err, order) => {
if (order) {
orderedItems.push(order.name);
}
});
}
console.log(orderedItems); // all the tasks below run before the loop finished looping
let orderData = new orderModel();
orderData._id = helpers.createRandomString(5).toUpperCase();
orderData.username = username;
orderData.orderCode = orderCode;
orderData.orderedItems = orderedItems;
orderData.totalPrice = 5;
orderData.save((err) => {
if (err) {
console.log(err);
callback(500, {
'Error': '1'
});
}
callback(200, {
'Message': 'Successfully ordered'
});
});
as #RishikeshDhokare said everything is executed asynchronously. so the trick is to split the tasks into separate functions.
so first we execute an async function you can use promises or async await
then we say after all the async tasks have been completed do the save task.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
const orderCode = [1, 2, 3]
const menuModel = {
findOne: item => new Promise(resolve => resolve({
_id: item._id,
name: 'name'
}))
}
class orderModel {
save(cb) {
return cb(null)
}
}
/*
ignore all above here i'm mocking your funcs
and vars so that the code works
*/
let orderedItems = [];
function getAllOrderedItems() {
const promises = orderCode.map(id => {
return menuModel.findOne({
_id: id
});
})
console.log('...start the request')
// fire all async items then resolve them all at once
return Promise.all(promises)
.then((data) => {
console.log('...finished all requests')
return orderedItems.concat(data);
})
.catch(err => console.log(err))
}
function handleSave(data) {
let orderData = new orderModel();
console.log(data)
console.log('...start save')
orderData.save((err) => {
console.log('...save finished')
});
}
//do all the async tasks then do the save task
getAllOrderedItems()
.then((data) => handleSave(data))

Un-nest JavaScript Firestore Functions

How can I rewrite this code to not be nested in itself? I also need access to the values obtained in the previous functions calls.
return docRef2.doc(`/users_stripe/${context.params.userID}`).get()
.then(snapshot => {
console.log("augu", snapshot);
return stripe.customers.createSource( jsonParser(snapshot._fieldsProto.id, "stringValue"),
{ source: jsonParser(snap._fieldsProto.token, "stringValue") },
function(err, card) {
console.log("listen people", card);
return docRef2.doc(`/users_stripe/${context.params.userID}/ptypes/ptypes`)
.set(card);
});
})
I am not sure what your code is doing here. I tried to write a pseudo/sample code that might give you an idea.
My code is not checked, so might contain problem.
let fun1 = () => {
return new Promise((resolve, reject) => {
docRef2.doc('/route').get().then(snapshot => {
if( snapshot ) resolve(snapshot);
else reject(snapshot);
})
})
}
let fun2 = (snapshot) => {
return new Promies((resolve, reject)=>{
stripe.customers.createSource(jsonParser(snapshot._fieldsProto.id, "stringValue"),
{ source: jsonParser(snap._fieldsProto.token, "stringValue") },
function (err, card) {
if (err) reject(false);// or whatever you wanna return
else resolve(card);
});
})
}
async function fun(){
let res1 = await fun1(); // Should contain snapshot
let res2 = await fun2(res1); // Should contain card
return docRef2.doc(`/users_stripe/${context.params.userID}/ptypes/ptypes`)
.set(card);
}

missing timing from promised value

So I am using Forge with View API to analyze all parts from a model which contain concrete and hide everything that is not concrete. The problem is that the properties for checking concrete are called from a DB which requires me to make it a promise. Checking for concrete is working as expected and then the problem starts. I return the Ids containing concrete, but my function which is supposed to hide it uses the Ids before the promise is resolved, so the array is empty.
console.log logs it as expected but everything else misses the timing.
My code:
getProperties(dbId) {
return new Promise((resolve, reject) => {
this.gui.getProperties(
dbId,
args => {
resolve(args.properties)
},
reject
)
})
}
async getConcreteIds() {
let wallfloorids = this.getWallIds().concat(this.getFloorIds());
let concreteIds = [];
for (let id of wallfloorids) {
let p1 = this.view.getProperties(id);
p1.then(props => {
for (let prop of props) {
if (prop.displayCategory === "Materialien und Oberflächen" && prop.displayValue.contains("Concrete")) {
concreteIds.push(id);
}
}
}).catch(() => {
});
}
return new Promise((resolve, reject) => {
try {
resolve(concreteIds)
} catch (e) {
console.log("Err", reject)
}
})
}
async onlyConcrete() {
this.getConcreteIds().then(concrete => {
debugger;
this.viewer.isolateById(concrete)
});
}
Map an array of promises for your loop and use Promise.all() to resolve after all the promises in loop resolve
Something like:
async getConcreteIds() {
let wallfloorids = this.getWallIds().concat(this.getFloorIds());
let concreteIds = [];
let promises = wallfloorids.map(id => {
let p1 = this.view.getProperties(id);
return p1.then(props => {
for (let prop of props) {
if (prop.displayCategory === "Materialien und Oberflächen" && prop.displayValue.contains("Concrete")) {
concreteIds.push(id);
}
}
});
});
return Promise.all(promises)
.then(_ => concreteIds)
.catch(err => console.log("Err", err))
}

Categories