2

I just start using nodejs/mongoose and I think I have a classical async problem. Could someone guide me how to fix this async problem?

I have this function "getAreasRoot" and inside I have a loop to populate children with the result of another async function. How can I fix it with the async library?

areaSchema.statics.getAreasRoot = function(cb: any) {
    let self = this;
    return self.model("Area").find({ parentId: null }, function(err: any, docs: any){
        docs.forEach(function(doc: any){
            doc.name = "Hi " + doc.name;
            doc.children = self.model("Area").getAreasChildren(doc._id, function(err: any, docs: any){});
        })
        cb(err, docs);
    });
};

areaSchema.statics.getAreasChildren = function(id: any, cb: any) {
    return this.model("Area").find({ parentId: null }).exec(cb);
}
2
  • 1
    Possible duplicate of How can I use mongoose functions inside a for loop? Commented Sep 28, 2017 at 4:56
  • @KevinB Not really a duplicate if the OP is asking how to use it with async.js. None of the answers provided uses async.js. Commented Sep 28, 2017 at 7:23

1 Answer 1

0

You have 2 tasks: get the roots and then get the children using the roots.

If I were to do this using async.js, I would use a combination of async.waterfall and async.mapSeries. We use .waterfall because we want to pass the results from the first task to the second task. We use .mapSeries because we want to alter each root area with its name and children.

areaSchema.statics.getAreasRoot = function (cb) {
    let self = this;
    async.waterfall([
        // every task has a callback that must be fired at least/most once
        // to tell that the task has finished OR when an error has occurred
        function getRoots (cb1) {
            self.find({ parentId: null }, cb1);
        },
        function getChildren (roots, cb2) {
            async.mapSeries(roots, function (root, cb3) {
                // inside this block, we want to fire the innest callback cb3 when 
                // each iteration is done OR when an error occurs to stop .mapSeries
                self.find({ parentId: root._id }, function (err, children) {
                    // error: stop .mapSeries
                    if (err)
                        return cb3(err);
                    root.name = "Hi " + root.name;
                    root.children = children;
                    // done: send back the altered document
                    cb3(null, root);
                });
            // the last argument is fired when .mapSeries has finished its iterations
            // OR when an error has occurred; we simply pass the inner callback cb2
            }, cb2) 
        }
    // the last argument is fired when .waterfall has finished its tasks
    // OR when an error has occurred; we simply pass the original callback cb
    ], cb);
};

To use it

Area.getAreasRoot(function (err, areas) {
    console.log(err, areas);
})

Aside

Mongoose operations are asynchronous, so

doc.children = self.model("Area").getAreasChildren(...) 

is not correct as you are returning a Promise opposed to the actual documents.

Also

You might be able to simplify your logic with virtual population or aggregation.

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

3 Comments

Thank you very much. Perfect documented answer
Is there any easy way to get also the children of the children and so on?
@Michalis I had this feeling you were going to ask this. Unfortunately, I do not know nor think it would be a good idea to do with async.js. You might want to rethink your schema. This might be helpful.

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.