2

I got a little bit of a problem. Here is the code:

Situation A:

var foundRiders = [];

            riders.forEach(function(rider){
                Rider.findOne({_id: rider}, function(err, foundRider){
                    if(err){
                        console.log("program tried to look up rider for the forEach loop finalizing the results, but could not find");
                    } else {
                        foundRiders.push(foundRider);
                        console.log(foundRiders);
                    }
                });
            });

Situation B

var foundRiders = [];

            riders.forEach(function(rider){
                Rider.findOne({_id: rider}, function(err, foundRider){ 
                    if(err){
                        console.log("program tried to look up rider for the forEach loop finalizing the results, but could not find");
                    } else {
                        foundRiders.push(foundRider);
                    }
                });
            });
            console.log(foundRiders);

So in Situation A when I console log I get that foundRiders is an array filled with objects. In situation B when I put the console.log outside the loop, my roundRiders array is completely empty...

How come?

4
  • 3
    it's async, your console.log gets called before the query is finished. That's why you are passing your callback function Commented Dec 11, 2017 at 15:35
  • 1
    console.log(foundRiders); is executed before the Rider.findOne is fineshed executing as it is asynchronous. Commented Dec 11, 2017 at 15:35
  • .......because why not ! and also nodejs is asynchronous Commented Dec 11, 2017 at 15:35
  • How do I delay it? I'm coming form PHP and this Asynchronous programming takes some time to get a hang of. Commented Dec 11, 2017 at 15:43

1 Answer 1

7

As others have said, your database code is asynchronous. That means that the callbacks inside your loop are called sometime later, long after your loop has already finishes. There are a variety of ways to program for an async loop. In your case, it's probably best to move to the promise interface for your database and then start using promises to coordinate your multiple database calls. You can do that like this:

Promise.all(riders.map(rider => {
    return Rider.findOne({_id: rider}).exec();
})).then(foundRiders => {
    // all found riders here
}).catch(err => {
    // error here
});

This uses the .exec() interface to the mongoose database to run your query and return a promise. Then, riders.map() builds and returns an array of these promises. Then,Promise.all()monitors all the promises in the array and calls.then()when they are all done or.catch()` when there's an error.


If you want to ignore any riders that aren't found in the database, rather than abort with an error, then you can do this:

Promise.all(riders.map(rider => {
    return Rider.findOne({_id: rider}).exec().catch(err => {
        // convert error to null result in resolved array
        return null;
    });
})).then(foundRiders => {
    foundRiders = foundRiders.filter(rider => rider !== null);
    console.log(founderRiders);
}).catch(err => {
    // handle error here
});

To help illustrate what's going on here, this is a more old fashioned way of monitoring when all the database callbacks are done (with a manual counter):

riders.forEach(function(rider){
    let cntr = 0;
    Rider.findOne({_id: rider}, function(err, foundRider){
        ++cntr;
        if(err){
            console.log("program tried to look up rider for the forEach loop finalizing the results, but could not find");
        } else {
            foundRiders.push(foundRider);
        }
        // if all DB requests are done here
        if (cntr === riders.length) {
            // put code here that wants to process the finished foundRiders
            console.log(foundRiders);
        }
    });
});

The business of maintaining a counter to track multiple async requests is what Promise.all() has built in.


The code above assumes that you want to parallelize your code and to run the queries together to save time. If you want to serialize your queries, then you could use await in ES6 with a for loop to make the loop "wait" for each result (this will probably slow things down). Here's how you would do that:

async function lookForRiders(riders) {
    let foundRiders = [];
    for (let rider of riders) {
        try {
            let found = await Rider.findOne({_id: rider}).exec();
            foundRiders.push(found);
        } catch(e) {
            console.log(`did not find rider ${rider} in database`);
        }
    }
    console.log(foundRiders);
    return foundRiders;
}

lookForRiders(riders).then(foundRiders => {
    // process results here
}).catch(err => {
    // process error here
});

Note, that while this looks like it's more synchronous code like you may be used to in other languages, it's still using asynchronous concepts and the lookForRiders() function is still returning a promise who's result you access with .then(). This is a newer feature in Javascript which makes some types of async code easier to write.

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

2 Comments

Really great answer! Unfortunately, this is what they did not taught during the complete webdeveloper course. I guess I gotta continue with the advanced web developer one.
@RutgerBrns - Since it looks like you may be fairly new on stack overflow, if this answered your question, then you can indicate that to the community by clicking the checkmark to the left of the answer. That will "accept" this answer and also earn you some reputation points on stack overflow for following the proper procedure here.

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.