Search code examples
node.jsasynchronousrecursiondeferred

NodeJS Asynchronous and Recursive


I have searched extensively and can not find an answer that seems to work. I have tried Q.deferred, async.series, async.each, I can not seem to get this sucker to work:

Here is the code, this works, however, the "return subTree" fires for the "tree" export before the recursive is complete. I have validated that the recursive is digging appropriately. I really need the return on recuriveChildren to wait until the recursion call is complete.

exports.tree = function(req, res) {
	var tree = {
		'name': 'New Account'
	};

	var methods = {};

	methods.recursiveChildren = function(path) {
		var subTree = {
			'name': path.field.label+":"+path.match+":"+path.value,
			'id': path._id,
			'parent': path.parent,
			'children': []
		}
		
		Path.find({parent:path._id}).sort({date_created:1}).exec(function (err,childpaths) {
			
			for ( var z in childpaths ) {
				var tmpTree = methods.recursiveChildren(childpaths[z]);
				subTree.children.push(tmpTree);
			}
			
		});
		
		return subTree;
		
	}


	Path.find({parent:null}).sort({date_created:1}).exec(function (err,paths) {
		tree.children = [];
		for ( var x in paths ) {

			var tmpTree = methods.recursiveChildren(paths[x]);
			tree.children.push(tmpTree);
		}

		res.jsonp(tree);
	});
 
};


Solution

  • The confusing thing about asynchronous patterns is that you can't return an actual value, because that hasn't happened yet. You can pass in a function to be executed once the asynchronous operation has completed (callback), or you can return an object that accepts a callback (a promise), that will execute the callback once the operation resolves the promise with a value.

    exports.tree = function(req, res) {
      var tree = {
        'name': 'New Account'
      };
    
      var methods = {};
    
      methods.recursiveChildren = function(path) {
        var subTree = {
          'name': path.field.label + ":" + path.match + ":" + path.value,
          'id': path._id,
          'parent': path.parent,
          'children': []
        }
    
        return new Promise(function(resolve, reject) {
          Path.find({
            parent: path._id
          }).sort({
            date_created: 1
          }).exec(function(err, childpaths) {
    
            Promise
              .all(childpaths.map(function(childpath) {
                /* collect a promise for each child path this returns a promise */
                return methods.recursiveChildren(childpath);
              }))
              .then(function(resolvedPaths) {
                subtree.children = resolvedPaths;
              
                 /* the top level promise is fulfilled with the subtree */
                resolve(subTree);
              });
          });
        });
      }
    
    
      Path.find({
        parent: null
      }).sort({
        date_created: 1
      }).exec(function(err, paths) {
        Promise.all(paths.map(function(path) {
          return methods.recursiveChildren(path);
        })).then(function(resolvedPaths) {
          tree.paths = resolvedPaths;
          res.jsonp(tree);
        });
      });
    
    };