我的Google云端功能存在承诺问题

人气:608 发布:2022-10-16 标签: javascript firebase google-cloud-firestore google-cloud-functions

问题描述

我的云功能中有一个http触发器正在运行,但是我收到一些奇怪的日志.我敢肯定我没有正确执行诺言.

I have an http trigger in cloud functions that is working, however I am getting some logs come back in a weird order. I am pretty sure I have not executed the promises correctly.

下面是代码:

exports.createGame = functions.https.onRequest((req, res) => {
    return cors(req, res, () => {

        // Check for POST request
        if (req.method !== "POST") {
            res.status(400).send('Please send a POST request');
            return;
        }
        const createGame = admin.firestore().collection('games')

        generatePin() 

        function generatePin() {
            ...pin generating code goes here...

            addGame(newGidToString)
        }

        function addGame(newGidToString) {
            var getGame = admin.firestore().collection('games')
            getGame = getGame.where('gid', '==', newGidToString)
            getGame.get().then(snapshot => {
                if (snapshot.empty) {

                    console.log('BODY ', req.body)

                    const promises = [];

                    const gameTitle = req.body.gametitle
                    const targetGID = req.body.target
                    const numPlayers = req.body.playercount.toString()

                    const newGID = newGidToString
                    const collections = ['games', 'clues', 'detours', 'messages', 'questions', 'tagstate']
                    collections.forEach(function (element) {
                        console.log('start loop', element)
                        let elementsRef = admin.firestore().collection(element)
                        elementsRef = elementsRef.where('gid', '==', targetGID)
                        elementsRef.get().then(snapshot => {
                            var batch = admin.firestore().batch()
                            snapshot.forEach(function (doc) {
                                // change the gid to the new game gid
                                let data = doc.data()
                                data.gid = newGID

                                if(data.template){
                                    data.template = false
                                }

                                if(!data.parent) {
                                    data.parent = targetGID
                                }

                                if (!data.playercount) {
                                    data.playercount = numPlayers
                                }

                                if (data.gametitle) {
                                    data.gametitle = gameTitle
                                }

                                let elementRef = admin.firestore().collection(element).doc()
                                batch.set(elementRef, data)
                            })
                            batch.commit().then(data => {
                                console.log('complete batch ', element)
                            })
                        })
                    })
                    res.send({ status: 200, message: 'Game: added.', pin: newGID})
                    console.log('completed');
                } else {
                    console.log('found a match ', snapshot)
                    // re-run the generator for a new pin
                    generatePin()
                }
            })
        }
    })

})

这是控制台日志的屏幕快照.

Here is a screen shot of the console logs.

注意日志的顺序.在循环结束时出现completed似乎是合乎逻辑的.

Notice the order of the logs. It would seem logical that completed would come at the end fo the loops.

非常感谢您的帮助.

更新:

function addGame(newGidToString) {
            var getGame = admin.firestore().collection('games')
            getGame = getGame.where('gid', '==', newGidToString)
            getGame.get().then(snapshot => {
                if (snapshot.empty) {

                    console.log('BODY ', req.body)

                    const promises = [];

                    const gameTitle = req.body.gametitle
                    const targetGID = req.body.target
                    const numPlayers = req.body.playercount.toString()

                    const newGID = newGidToString

                    const collections = ['games', 'clues', 'detours', 'messages', 'questions', 'tagstate']
                    collections.forEach(function (element) {

                        console.log('start loop', element)

                        let elementsRef = admin.firestore().collection(element)
                        elementsRef = elementsRef.where('gid', '==', targetGID)
                        // elementsRef.get().then(snapshot => {

                        const promGet = elementsRef.get();  //lets get our promise
                        promises.push(promGet);             //place into an array
                            promGet.then(snapshot => {  //we can now continue as before

                            var batch = admin.firestore().batch()

                            snapshot.forEach(function (doc) {
                                // change the gid to the new game gid
                                let data = doc.data()
                                data.gid = newGID

                                if(data.template){
                                    data.template = false
                                }

                                if(!data.parent) {
                                    data.parent = targetGID
                                }

                                if (!data.playercount) {
                                    data.playercount = numPlayers
                                }

                                if (data.gametitle) {
                                    data.gametitle = gameTitle
                                }

                                let elementRef = admin.firestore().collection(element).doc()
                                batch.set(elementRef, data)
                            })
                            batch.commit().then(data => {
                                console.log('complete batch ', element)
                            })

                        })
                    })
                    Promise.all(promises).then(() => {
                        res.send({ status: 200, message: 'Game: added.', pin: newGID })
                        console.log('completed');
                    });
                    // res.send({ status: 200, message: 'Game: added.', pin: newGID})
                    // console.log('completed');
                } else {
                    console.log('found a match ', snapshot)
                    // re-run the generator for a new pin
                    generatePin()
                }
            })
        }

第二次更新:(基于基思的回答)

SECOND UPDATE: (based off Keith's answer)

function addGame(newGidToString) {
            var getGame = admin.firestore().collection('games')
            getGame = getGame.where('gid', '==', newGidToString)
            getGame.get().then(snapshot => {
                if (snapshot.empty) {

                    console.log('BODY ', req.body)

                    const gameTitle = req.body.gametitle
                    const targetGID = req.body.target
                    const numPlayers = req.body.playercount.toString()

                    const newGID = newGidToString

                    const promises = [];

                    const collections = ['games', 'clues', 'detours', 'messages', 'questions', 'tagstate']
                    collections.forEach(function (element) {

                        console.log('start loop', element)

                        let elementsRef = admin.firestore().collection(element)
                        elementsRef = elementsRef.where('gid', '==', targetGID)

                        const getPromise = elementsRef.get();  //lets get our promise
                        promises.push(getPromise);             //place into an array
                        getPromise.then(snapshot => {  //we can now continue as before

                            var batch = admin.firestore().batch()

                            snapshot.forEach(function (doc) {
                                // change the gid to the new game gid
                                let data = doc.data()
                                data.gid = newGID

                                if(data.template){
                                    data.template = false
                                }

                                if(!data.parent) {
                                    data.parent = targetGID
                                }

                                if (!data.playercount) {
                                    data.playercount = numPlayers
                                }

                                if (data.gametitle) {
                                    data.gametitle = gameTitle
                                }

                                let elementRef = admin.firestore().collection(element).doc()
                                batch.set(elementRef, data)
                            })
                            batch.commit()
                        })
                    })
                    Promise.all(promises).then(() => {
                        res.send({ status: 200, message: 'Game: added.', pin: newGID })
                        console.log('completed from promise');
                    });
                } else {
                    console.log('found a match ', snapshot)
                    // re-run the generator for a new pin
                    generatePin()
                }
            })
        }

推荐答案

在使用Promise循环时,要记住的一件事是,如果您执行forEach,就不会等待.

When looping with promises, one thing to remember is if you do a forEach, it's not going to wait.

看来,OP希望做的是在他完成任务之前进行一次Res发送,以确保所有的诺言都已完成.

It appears that what the OP wants to do is before he hits the complete, and does a res send, wants to make sure all promises have been completed.

Promise.all https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all 就是这样做的.

因此,每次创建承诺时,如果将其推送到数组中,我们以后都可以使用Promise.all来确保所有承诺均已完成.

So every time you create a promise if you push this onto an array, we can later use Promise.all to make sure all the promises are complete.

所以如果我们替换

elementsRef.get().then(snapshot => {

使用

const promGet = elementsRef.get();  //lets get our promise
promises.push(promGet);             //place into an array
promGet.then(snapshot => {          //we can now continue as before

现在将您的诺言存储起来,以便以后使用,以便Promise.all可以检查以确保一切都已完成.您可以一行完成此操作,但是我觉得这样比较容易发现.例如->

Your promise is now stored for later so that Promise.all can check to make sure everything has complete. You could do this in one line, but I feel it's easier to see what's happening this way. eg->

promises.push(promGet.then(snapshot => {

最后,在执行res.send之前,我们已经有了我们的promise数组,请确保它们都已完成.

Finally now we have got our promise array, before we do our res.send, lets make sure they have all completed.

Promise.all(promises).then(() => {
  res.send({ status: 200, message: 'Game: added.', pin: newGID})
  console.log('completed');
});

以这种方式使用Promises时要记住的一件事是,您同时有很多事情在进行,有时这可能是个问题.连接到某些服务可能会限制并发连接的数量等.因此,在这些情况下,串联或混合使用是个好主意.甚至有可以并发的库,例如.一次只做X笔诺言.

One thing to keep in mind when using Promises this way, is that you have a fair few things going on at the same time, sometimes this can be an issue, eg. connected to some service might put restrictions on the number of concurrent connections etc. So in these cases it might be an idea doing things in series or a mix of both. There are even libs that can do concurrecy, eg. Do only X amounts of promises at a time.

最后,如果可以使用async / await,我建议您使用它们,因为它使Promises变得更易于使用.

And finally, if you can use async / await, I'd advice using them, as it makes Promises so much nicer to work with.

127