admin管理员组

文章数量:1319002

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 pleted before the queries have a chance to finish.

P.s - this is being ran through Cloud Functions using Admin SDK.

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 pleted before the queries have a chance to finish.

P.s - this is being ran through Cloud Functions using Admin SDK.

Share Improve this question edited Aug 31, 2019 at 15:48 Sha-1 asked Aug 31, 2019 at 15:37 Sha-1Sha-1 1975 silver badges13 bronze badges 2
  • Asynchronous programming can be difficult, but there's not enough information here to know what might be going wrong. You mentioned that you're using Cloud Functions - please edit the question to show the entire function. Dealing with promises correctly in Cloud Functions is extremely important. As it stand now, all I can see is that outputList will only be populated after the queries finish, and there's nothing waiting on those queries to bee plete. – Doug Stevenson Commented Aug 31, 2019 at 15:45
  • I've added the entire function. How would i go about waiting on the queries to plete? – Sha-1 Commented Aug 31, 2019 at 15:49
Add a ment  | 

3 Answers 3

Reset to default 7

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 plete. The array will be populated only after they are all plete, 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 plete:

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./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
            }])
        });
    });
}

本文标签: javascriptFirestore query for loop with multiple valuesStack Overflow