Firestore查询循环多个值

4

我正在尝试使用字符串中保存的数据检索多个Firestore文档。我的想法是对于数组中的每个值,使用Firestore查询来检索与该查询匹配的文档,并将其推送到另一个数组中。但是,我在实现这一点时遇到了一些问题。到目前为止,我已经尝试过:

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));

});

我不是完全确定,但我认为问题之一是for循环在查询有机会完成之前就已经完成了。

P.s - 这是通过使用Admin SDK在云函数中运行的。


异步编程可能很困难,但这里没有足够的信息来知道可能出了什么问题。您提到正在使用Cloud Functions-请编辑问题以显示整个函数。在Cloud Functions中正确处理承诺非常重要。目前,我只能看到outputList将仅在查询完成后填充,并且没有任何东西等待这些查询完成。 - Doug Stevenson
我已经添加了整个函数。我该如何等待查询完成? - Sha-1
3个回答

7

您的queryRef实际上不是一个引用,而是一个承诺,在您的get/then/catch完成后解析。 您需要使用这些承诺来确定它们何时全部完成。 只有在它们全部完成之后,数组才会被填充,然后才可以安全地使用该数组发送响应。

将所有承诺收集到一个数组中,并使用Promise.all()获取一个新的承诺,该承诺在它们全部完成后解析:

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);
    })

});

你可能希望使用这些教程来更好地理解如何在云函数中处理 promises: https://firebase.google.com/docs/functions/video-series/

我了解到“尽量不要过多改变OP代码”并且这段代码可以工作,但还不够优化。使用“惯用”的方式是使用高阶列表函数,并通过从承诺中发出“返回值”,而不是依赖于副作用数组。 - Holli

0
你需要了解 Promise,不要像这样在循环中调用 Promise。首先,你需要将返回来自数据库的结果(异步)的代码分块,并使用 Promise.all() 来处理多个 Promise。
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;

0
这里是我尝试完全习惯用语解决方案。它不需要中间变量(不存在竞争条件),并且很好地分离了关注点。
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) );
});

我在测试中使用了一个简单的模型,因为我无法访问那个特定的数据库。但是它应该能够工作。

function query_data( search ) {
    return new Promise( (resolve, reject) => {
        setTimeout( () => { 
            resolve([{
                data: function() { return search.toUpperCase() },
                empty: false
            }])
        });
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接