Q:

Mongoose async call inside a loop with async library

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);
}

A:

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.

