Jason
Jason

Reputation: 1151

Having a promise issue with my google cloud function

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.

here is the code:

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.

enter image description here

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

Any help is great appreciated.

UPDATE:

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

Upvotes: 0

Views: 70

Answers (1)

Keith
Keith

Reputation: 24231

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

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/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all , is exactly what this does.

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.

So if we replace

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

with

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

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 => {

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

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.

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

Upvotes: 1

Related Questions