0

I´m trying to loop through the content of a DataSnapshot and then depending on a condition do some work FOR EACH one of the elements but currently, the ForEach is only doing the work in the first item. The "serverStatus" sometimes is waiting and sometimes in "onCall". When the first item is "onCall" does not go through the rest of the items as I think is supposed to do. Below a snapchot of where I get the information from:

enter image description here

And here is my function:

exports.manageCallRequests = functions.database.ref('/resquests/{userId}').onCreate((snap, context) => {
    const event = snap.val();

    console.log("function manageCallRequests is being called")
    var rootPath = admin.database().ref();
    var userOnCall = context.params.userId;

    var serversRef = rootPath.child('servers');
     var callRequest = event;
     var userTime = callRequest["time"];
     var waiting= "waiting";

  //We first get all the servers in ascending order depending on the last time they were used
  var serversSorted = serversRef.orderByChild('lastTimeUsed')


       //Gets the children on the "serversSorted" Query
       return serversSorted.once("value").then(allServers =>{
        //Checks if there is any child
            if(allServers.hasChildren()){



                    allServers.forEach(async function(server) {


                        //we extract the value from the server variable, this contains all the information 
                        //about each one of the servers we have
                        var serverInfo = server.val();
                        var serverKey = server.key;
                        var serverNumber = serverInfo["serverNumber"];
                        var serverStatus = serverInfo["serverStatus"];

console.log("server status "+serverStatus)
                        if(serverStatus === waiting){

                            const setCallRequest = await serversRef.child(serverKey).child("current").child("callRequest").set(callRequest);
                            const removeUserOnCall = await rootPath.child("resquests").child(userOnCall).remove();
                            const setServerStatus = await serversRef.child(serverKey).child("serverStatus").set("onCall");

                        }

                    });
                }else{
                    console.log("No servers available")


                }
            });

});
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
Fran Tardencilla
  • 289
  • 1
  • 2
  • 13
  • How are you observing this behavior? Are you logging anything? Can you show the output of that and compare it to what you expect? – Doug Stevenson Apr 03 '20 at 01:15
  • most likely nothing to do with Promises, more likely that `serverStatus === waiting` is false for all but the first iteration – Jaromanda X Apr 03 '20 at 01:15
  • That begs the question: what is the value of `waiting` in the above code? And how is it invoked in Cloud Functions? We're missing some essential information here that is necessary to be able to help. Also see [how to create a minimal, complete, verifiable example](http://stackoverflow.com/help/mcve). – Frank van Puffelen Apr 03 '20 at 01:40
  • I already edited my question. The function has the same behavior even when serverStatus === waiting is true – Fran Tardencilla Apr 03 '20 at 01:55

2 Answers2

0

I had the same behavior because my cloud function was exited before that all iterations were executed in the forEach loop.I get rid of it using this snippet of code:

for (const doc of querySnapshot.docs) {
    // Do wathever you want    
    // for instance:
    await doc.ref.update(newData);
}
Simon
  • 6,025
  • 7
  • 46
  • 98
  • This seems to be the write answer because I found this question https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop but I don´t know how to make the queryDataSnapshot iterable the ".docs" function is not working. – Fran Tardencilla Apr 03 '20 at 02:48
0

I found 2 ways of getting this done. The first one is useful if we have a DataSnapshot without any OrderBy* call, in this case, would be:

var allServers = await serversRef.once("value");

for (let serverKey of Object.keys(allServers.val())){
   var server = allServers[serverKey];
    //Do some work
}

We need to first get the keys of the object to then be able to extract it from within the for loop, as explained here otherwise we´ll get a "TypeError: 'x' is not iterable"

Now the problem with this particular case is that a have a DataSnapshot that was previously sorted at var serversSorted = serversRef.orderByChild('lastTimeUsed') so when we call Object.keys(allServers.val()) the value returned is no longer sorted and that´s where forEach() comes in handy. It guarantees the children of a DataSnapshot will be iterated in their query order as explained here however for some reasons when doing some async work within the forEach loop this seems not to work, that´s why I had to do this:

var serversSorted = serversRef.orderByChild('lastTimeUsed')
    var allServers = await serversSorted.once("value");

    //Checks if there is any children
    if (allServers.hasChildren()) {
        //if there is iterate through the event that was passed in containing all
        // the servers
        var alreadyOnCall = false;
        var arrayOfServers = []
        var arrayOfKeys = []

        allServers.forEach(function(individualServer){
                arrayOfKeys.push(individualServer.key)
                arrayOfServers.push(individualServer)
        })

        for (var serveIndex = 0; serveIndex < arrayOfServers.length;serveIndex++){

            var serverObj = arrayOfServers[serveIndex]
            var serverObject = serverObj.val()
            var serverKey = arrayOfKeys[serveIndex]

            var serverStatus = serverObject["serverStatus"]; 
            var serverNumber = serverObject["serverNumber"]; 

            console.log("server info "+serverStatus+" "+serverKey);

            if (serverStatus === waiting && alreadyOnCall === false) {
                const setCallRequest = await serversRef.child(serverKey).child("current").child("callRequest").set(callRequest);
                const removeUserOnCall = await rootPath.child("resquests").child(userOnCall).remove();
                const setServerStatus = await serversRef.child(serverKey).child("serverStatus").set("onCall");
                alreadyOnCall= true
                console.log("Call properly set");
            } 
        }


    }
Fran Tardencilla
  • 289
  • 1
  • 2
  • 13