1

I'm querying a mongo database to retrieve the tiles for the display in rougelike game. This is the function I use:

function get_display(){
    var collections = ['austinsroom'];
    var db  = mongojs(uri, collections);
    var temphtml = '';

    for(var j = 0; j < 3; j++) {
        console.log("y=" + String(j));
        db.austinsroom.find({"y": j}, {}).sort({"x": 1}, function(err, records) {
            if(err) {
                console.log("There was an error executing the database query.");
                return;
            } 
            var i = records.length;
            while(i--) {
                temphtml += records[i].display;
            }   
            temphtml += '<br>';
            //console.log(temphtml);
            //return temphtml;
            //THE ONLY WAY I CAN GET ANYTHING TO PRINT IN THE CONSOLE IS IF I PUT IT INSIDE THE LOOP HERE
            });
        //console.log(temphtml);
        //return temphtml;
        //THIS DOES NOTHING
    }
    //console.log(temphtml);
    //return temphtml;
    //THIS DOES NOTHING
}
get_display();

If I put the console.log(temphtml) inside the loop, it prints out three times which isn't what I want. I only want the final string (i.e. ...<br>...<br>...<br>. Also I can't ultimately return the temphtml string, which is actually the important thing. Is this some quirk of javascript? Why would it not execute statements after the loop?

Also: is there a better way to retrieve every element of a grid that's stored in a mongo database, in order, so it can be displayed properly? Here's what the mongo documents look like:

{
    "_id": {"$oid": "570a8ab0e4b050965a586957"},
    "x": 0,
    "y": 0,
    "display": "."
}

Right now, the game is supposed to display a "." in all empty spaces using the x and y values for the coordinates. The database is indexed by "x" values.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • I'm not sure what `db.austinsroom.find` is but is it an asynchronous function? If it is then that means the for loop might finish before the `db.austinsroom.find` call in which case the `temphtml` variable will have nothing in it... – IMTheNachoMan Apr 18 '16 at 20:56
  • 2
    @IMTheNachoMan Is correct about `db.austinsroom.find` being asynchronous (notice the call back function). Sounds like what you want is loop through `db.austinsroom.find` 1,2,3 THEN print. Since 1,2,3 contain asynchronous calls, you will need to find a way to know when they have all completed (or failed). Look into using promises – Karl Galvez Apr 18 '16 at 21:16
  • @KarlGalvez but there are ways to actually either "loop" with async flow control, or build the list of promises to return. Noting that the `mongojs` driver does not itself support promises, but other drivers for MongoDB do. – Neil Lunn Apr 18 '16 at 23:18

2 Answers2

5

See async.whilst. You want flow control of the for loop, for which this provides a callback to control each loop iteration.

var temphtml = "",
    j = 0;

async.whilst(
  function() { return j < 3 },
  function(callback) {
    db.austinsroom.find({"y": j }, {}).sort({"x": 1}, function(err, records) 
      temphtml += records.map(function(el) {
          return el.display;
      }).join("") + '<br>';
      j++;
      callback(err);
    });
  },
  function(err) {
     if (err) throw err;
     console.log(temphtml);
  }
)

Either that or use Promise.all() on collected promises to return "one big result". But you would also need to switch to promised-mongo from mongojs, as the nearest equivalent, since there are more mongodb drivers that actually support promises. That one is just the direct fork from mongojs:

var temphtml = "",
    j = 0,
    promises = [];

for ( var j=0; j < 3; j++ ) {
   promises.push(db.austinsroom.find({"y": j }, {}).sort({"x": 1}).toArray());
   promises.push('<br>');   // this will just join in the output
)

Promise.all(promises).then(function(records) {
    temphtml += records.map(function(el) {
        return el.display;
    }).join("");
})

Not exactly the same thing, since it's one list output and not three, but the point is that the Promise objects defer until actually called to resolve, so you can feed the paramters in the loop, but execute later.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • Your first solution worked great with after I npm installed async. I still don't quite understand why or promises, but that's totally on me :/ – Austin Capobianco Apr 18 '16 at 23:13
  • 1
    @AustinCapobianco What the answer is showing you is the `async.whilst` has a `callback` on the iterator, so the next loop is only called when that `callback` is called from inside the resulting `.find()` callback. Promises, mean that the `.find()` statements do not actually execute but actually return a "promise". These are then all "resolved" ( executed ) with the `.then()`. But with `Promise.all()` they happen all at once and "combine" output as a single array. Aside from dependencies, the difference is in sequence of execution. Either serial, or "all at once" respectively. – Neil Lunn Apr 18 '16 at 23:24
  • I still don't get the purpose of the `callback(err);` after an iteration. Also while `console.log(temphtml)` works, I can't figure out how to make the function return the `temphtml` string. It just says the result is undefined when I try `console.log(get_display());` – Austin Capobianco Apr 19 '16 at 00:56
  • 1
    @AustinCapobianco Because you are doing it wrong. Read ["How do I return the response from an asynchronous call?"](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) which basically contains the definitive answers on the subject. As to the `callback(err)`, keep looking and reading further and work it out. Once you understand callbacks it becomes quite obvious. – Neil Lunn Apr 19 '16 at 00:59
0

I do not use MongoDB but from what I am reading it is asynchronous. So what is happening is your db.austinsroom.find call fires another "thread" and returns to the for loop to continue the next iteration.

One way to do what you want is have a check at the end of your db.austinsroom.find function to see if you're done with the for loop. Something like:

function get_display()
{
    var collections = ['austinsroom'];
    var db  = mongojs(uri, collections);
    var temphtml = '';
    var doneCounter = 0;

    for(var j = 0; j < 3; j++)
    {
        console.log("y = " + String(j));
        db.austinsroom.find({"y": j}, {}).sort({"x": 1}, function(err, records)
        {
            if(err)
            {
                console.log("There was an error executing the database query.");
                return;
            } 
            var i = records.length;
            while(i--)
            {
                temphtml += records[i].display;
            }   
            temphtml += '<br>';

            // we're done with this find so add to the done counter
            doneCounter++;

            // at the end, check if the for loop is done
            if(doneCounter == 3)
            {
                // done with all three DB calls
                console.log(temphtml);
            }
       });
    }
}

This is probably not the best way as I know nothing about MongoDB but it should put you on the right path.

IMTheNachoMan
  • 5,343
  • 5
  • 40
  • 89
  • Tried it, but it didn't work. This doesn't make any sense to me. I even tried "j=1" and "j=2" in the if statement, but nothing worked... – Austin Capobianco Apr 18 '16 at 21:53
  • Promises are still the better answer IMO, but will require getting your mongodb calls to return them. However, I think this solution will work if you use `function(err, records, j)` currently this function doesn't know about `j`(i think). If not, try to log the value of j before the `if(j == 3)` statement – Karl Galvez Apr 18 '16 at 22:27
  • When I call `console.log(temphtml)` in the if statement, it just gives me the blank `temphtml=''` from when I initialized it, regardless of what j is. The j value is coming out fine. I'm trying to figure out promises now, but I don't really understand them. – Austin Capobianco Apr 18 '16 at 22:35
  • I see the issue now. The order the `db.austinsroom.find` is not guaranteed to be in 1,2,3 order. – IMTheNachoMan Apr 19 '16 at 15:21
  • And yes, `Promise` would be better but not sure how much of a pain it'll be to implement with MongoDB. – IMTheNachoMan Apr 19 '16 at 15:23