Reputation: 197
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.
Upvotes: 4
Views: 5174
Reputation: 5072
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
}])
});
});
}
Upvotes: 0
Reputation: 2111
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;
Upvotes: 0
Reputation: 317322
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/
Upvotes: 7