1

I have a simple tree with ids that are keys to a Mongo collection. I'm using a node library called treewalker. As I walk each node of the tree, I'm trying to look up the name (using mongoose) and simply append it to the current node. If I don't do a callback to lookup the node name, and just use some fixed value, I get the value I'm expecting. Let me illustrate in code:

Here is my tree:

{
  "categoryTree": [
    {
      "categoryId": "1",
      "children": [
        {
          "categoryId": "2",
          "children": [
            {
              "categoryId": "3",
              "children": []
            },
            {
              "categoryId": "4",
              "children": []
            }
          ]
        },
        {
          "categoryId": "5",
          "children": []
        },
        {
          "categoryId": "6",
          "children": []
        }
      ]
    },
    {
      "categoryId": "7",
      "children": [
        {
          "categoryId": "8",
          "children": []
        }
      ]
    }
  ]
}

Here is code that does what I want:

catTree.categoryTree.forEach(function(node){
    var counter = 0;
    tree.walkTree(node, 'children', function(obj){
        obj.name = counter++;
    });
});
//This tree has the names (as the counter above) in it as I expect
console.log(JSON.stringify(catTree));

However, as soon as I throw in a mongoose callback to get the category name, the category tree that's printed no longer has the names.

catTree.categoryTree.forEach(function(node){
    tree.walkTree(node, 'children', function(obj){
        //Cat is a mongoose model defined elsewhere
        Cat.findById(obj.categoryId, {_id:0,name:1}).exec(function(err, value){
            obj.name = value.name;
        });
    });
});
//This tree has NO names :(
console.log(JSON.stringify(catTree));

I know this is an issue of timing, but I can't figure out how to solve it. I've seen several SO Articles like this one that suggest tracking callbacks and continuing only after they've all been called. I can't figure out how to apply that pattern to my case because I'm walking a tree and not just iterating a flat list. I'm starting to think that my problem might that I'm using the treewalker library, vs. just writing my own algorithm with callbacks after each node is visited.

I really appreciate your help!

Community
  • 1
  • 1
tobyb
  • 696
  • 9
  • 18

1 Answers1

2

You database calls are asynchronous. That means they complete some time in the future, long after the .forEach() iteration has finished. If your database can handle a whole tree of queries being thrown at it at once (running all those queries essentially in parallel), then you can do something as simple as this:

let cntr = 0;
catTree.categoryTree.forEach(function(node){
    tree.walkTree(node, 'children', function(obj){
        //Cat is a mongoose model defined elsewhere
        ++cntr;
        Cat.findById(obj.categoryId, {_id:0,name:1}).exec(function(err, value){
            --cntr;
            if (!err) {
                obj.name = value.name;
            }
            // see if all requests are done
            if (cntr === 0) {
                console.log(JSON.stringify(catTree));
            }
        });
    });
});

Anytime you're trying to coordinate more than one asynchronous operation, it usually makes sense to use promises (since that's exactly what they were built for) and mongoose has promises built in for queries. Here you collect a promise from each query into an array and then Promise.all() to tell you when they are all done.

let promises = [];
catTree.categoryTree.forEach(function(node){
    tree.walkTree(node, 'children', function(obj){
        //Cat is a mongoose model defined elsewhere
        let p = Cat.findById(obj.categoryId, {_id:0,name:1}).exec().then(function(value) {
            obj.name = value.name;
        });
        promises.push(p);
    });
});

Promise.all(promises).then(function() {
    console.log(JSON.stringify(catTree));
}).catch(function(err) {
    // error
    console.log(err);
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • This is awesome! Thanks for the answer and the promises explanation. If I could buy you a beer, I would. – tobyb May 06 '17 at 22:35