0

The issue I am having is that I need to get a total count of a collection of objects, which has the form of a tree where each object can also contain deeply nested objects. The collection of data I start with already has one nested layer available, but to find out if there is a third or more nested layers in an object, an API call has to be made with the nested object's id, which returns the next nested object if it exists. So, currently I have something like this:

function getCount(thread) {
  var deferred = $q.defer();
  var count = 0;
  function getComment(comment) {
    count++;

//if nested comments exist in data
    if (comment.fld.nested) {
      _.each(comment.fld.nested, function(x) {
        getComment(x);
      });
      deferred.resolve(count);

    } else if (comment.meta) {

//if not, load more from API with id
      return PostService.getComment(comment.meta.id).then(function(comment){
        if (comment.fld.nested) {
          _.each(comment.fld.nested, function(x) {
            return getComment(x);
          });
        }
        return count;
      });
    }
    return deferred.promise;
  }
  _.each(thread.fld.nested, function(x) {
    return getComment(x);
  });
return deferred.promise;
}


getCount(c).then(function(x) {
  console.log('final count:', x);
});

Right now, I can get the count for all objects nested to 2nd level deep, but anything loaded from the API promise is not included in the count when I call the getCount().then() function. How do I make this wait until all promises are resolved so I can get the final count returned?

  • your `getCount` function doesn't return anything - I can't see how anything gets "logged" - I'd expect an error like `getCount(...) is undefined` – Jaromanda X Apr 17 '17 at 04:18
  • @JaromandaX it does, just forgot to copy it out. returns the deferred.promise right now. – Rick Sanchez Apr 17 '17 at 04:29
  • the only issue now is that you are returning a promise ... that has a single point of resolution - which is called recursively - once a promise is resolved, any other attempts to resolve it will be *silently ignored* – Jaromanda X Apr 17 '17 at 04:51
  • 2
    the 3 bits of code that look like `_.each(..., function(x) { return getComment(x); })` wont "wait" for the asynchronous code to "complete" - so all those have to be rewritten – Jaromanda X Apr 17 '17 at 04:56
  • 1
    I can't be sure - but https://jsfiddle.net/30cy2do8/ may be more along the lines of what you need – Jaromanda X Apr 17 '17 at 04:57
  • @JaromandaX That jsfiddle got me very very close - Just needed to add one more Promise.all in the PostService and it now works! Thank you! – Rick Sanchez Apr 17 '17 at 06:24

2 Answers2

0

Try removing the parameter "comment" from the API call then method:

From: function(comment){}

To: function(){}

Omeri
  • 187
  • 2
  • 16
0

As Jaromanda X mentioned in the comments, you're having getComment return an asynchronous promise, but you're not waiting for the results.

One aspect hindering you is that you're using deferred-style promises; if you were to use then, you'll have the benefit of being able to return a promise in a then handler, which will cause the outer promise to wait on the inner promise.

// foo won't resolve until Promise Two resolves.
var foo = getPromiseOne().then(function(valueOne) {
  return getPromiseTwo(valueOne);
});

You'll be waiting for a lot of promises in parallel, so I'm going to switch to Promise.all ($q.all) and Array.map (_.map) so it's clear what you're waiting for.

function getCount(thread) {
  var count = 0;
  function getComment(comment) {
    count++;

//if nested comments exist in data
    if (comment.fld.nested) {
      return $q.all(_.map(comment.fld.nested, function(x) {
        return getComment(x);
      }));
    } else if (comment.meta) {

//if not, load more from API with id
      return PostService.getComment(comment.meta.id).then(function(comment){
        if (comment.fld.nested) {
          return $q.all(_.map(comment.fld.nested, function(x) {
            return getComment(x);
          }));
        }
      });
    }
    return;  // Count matters, not return values, so returning undefined is fine.
  }

  // Wait for all nested promises to resolve, but ignore the array result
  // and return the count instead.
  return $q.all(_.map(thread.fld.nested, function(x) {
    return getComment(x);
  }).then(function() { return count; });
}

getCount(c).then(function(x) {
  console.log('final count:', x);
});
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Thanks, this is also very close to the solution @jaromandaX gave above: jsfiddle.net/30cy2do8 which got me on the right track. – Rick Sanchez Apr 17 '17 at 06:33
  • Although I saw @jaromandaX answer first, I accepted your answer since it is near the same and I can't seem to award to his comments. – Rick Sanchez Apr 17 '17 at 06:38
  • I hadn't clicked through to their fiddle; yes, our answers are very similar. Comments can't be selected as answers, but if they post their code as an answer, please don't hesitate to unaccept mine and accept theirs instead. Glad your problem is solved! – Jeff Bowman Apr 17 '17 at 07:34