Mongoose async call inside a loop with async library
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.