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.

3
  • 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... Commented Apr 18, 2016 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 Commented Apr 18, 2016 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. Commented Apr 18, 2016 at 23:18

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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 :/
@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.
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());
@AustinCapobianco Because you are doing it wrong. Read "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.
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.

5 Comments

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...
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
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.
I see the issue now. The order the db.austinsroom.find is not guaranteed to be in 1,2,3 order.
And yes, Promise would be better but not sure how much of a pain it'll be to implement with MongoDB.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.